blob: 452d47aac1c17d9670de9a262ccac30d8e05c1bc [file] [log] [blame]
Xixuan Wu0c76d5b2017-08-30 16:40:17 -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 for tasks triggered by suite scheduler."""
Xixuan Wu244e0ec2018-05-23 14:49:55 -07006# pylint: disable=dangerous-default-value
Xixuan Wu0c76d5b2017-08-30 16:40:17 -07007
8from distutils import version
Garry Wangdce77572021-07-18 19:33:35 -07009from collections import namedtuple, defaultdict
Xixuan Wu0c76d5b2017-08-30 16:40:17 -070010import logging
Xinan Lindf0698a2020-02-05 22:38:11 -080011import uuid
Xixuan Wu0c76d5b2017-08-30 16:40:17 -070012
Xinan Lindf0698a2020-02-05 22:38:11 -080013import analytics
Xixuan Wu0c76d5b2017-08-30 16:40:17 -070014import build_lib
15import task_executor
Xixuan Wu0c76d5b2017-08-30 16:40:17 -070016import tot_manager
17
18# The max lifetime of a suite scheduled by suite scheduler
Sean Abrahamec0d0762020-09-18 17:19:05 +000019_JOB_MAX_RUNTIME_MINS_DEFAULT = 72 * 60
Xixuan Wu0c76d5b2017-08-30 16:40:17 -070020
Garry Wangdce77572021-07-18 19:33:35 -070021# BuildTarget contains information for a hwtest target.
22BuildTarget = namedtuple('BuildTarget', ['board', 'model', 'cros_build'])
23
Xixuan Wu0c76d5b2017-08-30 16:40:17 -070024
25class SchedulingError(Exception):
26 """Raised to indicate a failure in scheduling a task."""
27
28
29class Task(object):
30 """Represents an entry from the suite_scheduler config file.
31
32 Each entry from the suite_scheduler config file maps one-to-one to a
33 Task. Each instance has enough information to schedule itself.
34 """
35
Xinan Lin33937d62020-04-14 14:41:23 -070036 def __init__(self,
37 task_info,
38 board_family_config={},
39 tot=None,
40 is_sanity=False):
Xixuan Wu0c76d5b2017-08-30 16:40:17 -070041 """Initialize a task instance.
42
43 Args:
44 task_info: a config_reader.TaskInfo object, which includes:
45 name, name of this task, e.g. 'NightlyPower'
46 suite, the name of the suite to run, e.g. 'graphics_per-day'
47 branch_specs, a pre-vetted iterable of branch specifiers,
48 e.g. ['>=R18', 'factory']
49 pool, the pool of machines to schedule tasks. Default is None.
50 num, the number of devices to shard the test suite. It could
51 be an Integer or None. By default it's None.
Taylor Clark54427ce2021-02-18 21:59:27 +000052 analytics_name, the build rule name. Initially build rule or test rule
53 name was used in suite scheduler's dashboard for analytics. Later it
54 was expanded to the entire pipeline and we want to tag all requests
55 from Suite Scheduler with the rule name.
Po-Hsien Wangdd833072018-08-16 18:09:20 -070056 board_families, a common separated list of board family to run this
57 task on. Boards belong to one of the board family in this list
58 would be added to task_info.boards.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -070059 boards, a comma separated list of boards to run this task on. Default
Po-Hsien Wangdd833072018-08-16 18:09:20 -070060 is None, which allows this task to run on all boards. If same board
61 is specified in 'boards' and 'exclude_boards', we exclude this
62 board.
Xinan Linc8647112020-02-04 16:45:56 -080063 dimensions, a comma separated lists of labels. Each label is in
64 the form of 'key:value'.
Po-Hsien Wangdd833072018-08-16 18:09:20 -070065 exclude_board_families, a common separated list of board family not to
66 run task on. Boards belong to one of the board family in this list
67 would be added to task_info.exclude_boards.
Po-Hsien Wang6d589732018-05-15 17:19:34 -070068 exclude_boards, a comma separated list of boards not to run this task
69 on. Default is None, which allows this task to run on all boards.
Po-Hsien Wangdd833072018-08-16 18:09:20 -070070 If same board is specified in 'boards' and 'exclude_boards', we
71 exclude this board.
Xixuan Wu89897182019-01-03 15:28:01 -080072 models, a comma separated list of models to run this task on. Default
73 is None, which allows this task to run on all models. If same model
74 is specified in 'models' and 'exclude_models', we exclude this
75 model.
76 exclude_models, a comma separated list of models not to run this task
77 on. Default is None, which allows this task to run on all models.
C Shapiro09108252019-08-01 14:52:52 -050078 any_model, set to True to not pass the model parameter and allow
79 a test suite to run any/all models available for testing.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -070080 priority, the string name of a priority from constants.Priorities.
81 timeout, the max lifetime of the suite in hours.
82 cros_build_spec, spec used to determine the ChromeOS build to test
83 with a firmware build, e.g., tot, R41 etc.
84 firmware_rw_build_spec, spec used to determine the firmware RW build
85 test with a ChromeOS build.
86 firmware_ro_build_spec, spec used to determine the firmware RO build
87 test with a ChromeOS build.
Brigit Rossbachbb080912020-11-18 13:52:17 -070088 firmware_ro_version, pinned firmware RO version.
89 firmware_rw_version, pinned firmware RW version.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -070090 test_source, the source of test code when firmware will be updated in
91 the test. The value can be 'firmware_rw', 'firmware_ro' or 'cros'.
92 job_retry, set to True to enable job-level retry. Default is False.
Xixuan Wu80531932017-10-12 17:26:51 -070093 no_delay, set to True to raise the priority of this task in task.
94 force, set to True to schedule this suite no matter whether there's
95 duplicate jobs before.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -070096 queue, so the suite jobs can start running tests with no waiting.
97 hour, an integer specifying the hour that a nightly run should be
98 triggered, default is set to 21.
99 day, an integer specifying the day of a week that a weekly run should
100 be triggered, default is set to 5 (Saturday).
101 os_type, type of OS, e.g., cros, brillo, android. Default is cros.
102 The argument is required for android/brillo builds.
103 launch_control_branches, comma separated string of launch control
104 branches. The argument is required and only applicable for
105 android/brillo builds.
106 launch_control_targets, comma separated string of build targets for
107 launch control builds. The argument is required and only
108 applicable for android/brillo builds.
109 testbed_dut_count, number of duts to test when using a testbed.
Xinan Lin4757d6f2020-03-24 22:20:31 -0700110 qs_account, quota account for the unmanaged pool which has enabled
111 Quota Scheduler.
Garry Wangdce77572021-07-18 19:33:35 -0700112 multi_dut_boards, a common separated list of strings to specify
113 board family in a multi-DUTs testing. Each string contains two
114 or more boards that separated by semicolon, where the first board
115 will be treated as primary board while the rest are secondaries.
116 If this args is not None then multi_dut_models args will be
117 ignored as they cannot be used together.
118 multi_dut_models, a common separated list of strings to specify
119 models of DUTs expected in a multi-DUTs testing. Each string
120 contains two or more models that separated by semicolon, where the
121 first model will be treated as primary model while the rest are
122 secondaries.
123 multi_dut_trigger, a string to specify how should we trigger a
124 multi-DUTs testing. E.g. 'primary': when the primary board has
125 a new build, or 'all': when primary and all secondary has a new
126 build.
Jacob Kopczynskic54520f2020-08-07 13:20:12 -0700127
Xixuan Wu83118dd2018-08-27 12:11:35 -0700128 board_family_config: A board family dictionary mapping board_family name
129 to its corresponding boards.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700130 tot: The tot manager for checking ToT. If it's None, a new tot_manager
131 instance will be initialized.
Xinan Lin33937d62020-04-14 14:41:23 -0700132 is_sanity: A boolean; true if we are running in sanity env.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700133 """
Xixuan Wu5451a662017-10-17 10:57:40 -0700134 # Indicate whether there're suites get pushed into taskqueue for this task.
135 self.is_pushed = False
136
Taylor Clark8b06a832021-02-16 21:45:42 +0000137 self.analytics_name = task_info.analytics_name
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700138 self.branch_specs = task_info.branch_specs
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700139 self.cros_build_spec = task_info.cros_build_spec
Xixuan Wu008ee832017-10-12 16:59:34 -0700140 self.day = task_info.day
Jacob Kopczynskic1d0f5c2020-08-09 10:41:32 -0700141 self.firmware_ro_build_spec = task_info.firmware_ro_build_spec
142 self.firmware_rw_build_spec = task_info.firmware_rw_build_spec
Brigit Rossbachbb080912020-11-18 13:52:17 -0700143 self.firmware_ro_version = task_info.firmware_ro_version
144 self.firmware_rw_version = task_info.firmware_rw_version
Jacob Kopczynskic1d0f5c2020-08-09 10:41:32 -0700145 self.force = task_info.force
146 self.frontdoor = task_info.frontdoor
147 self.hour = task_info.hour
148 self.job_retry = task_info.job_retry
149 self.name = task_info.name
150 self.no_delay = task_info.no_delay
151 self.num = task_info.num
Craig Bergstrom58263d32018-04-26 14:11:35 -0600152 self.only_hwtest_sanity_required = task_info.only_hwtest_sanity_required
Jacob Kopczynskic1d0f5c2020-08-09 10:41:32 -0700153 self.os_type = task_info.os_type
154 self.pool = task_info.pool
155 self.priority = task_info.priority
Xinan Lin4757d6f2020-03-24 22:20:31 -0700156 self.qs_account = task_info.qs_account
Jacob Kopczynskic1d0f5c2020-08-09 10:41:32 -0700157 self.suite = task_info.suite
158 self.test_source = task_info.test_source
159 self.testbed_dut_count = task_info.testbed_dut_count
160 self.timeout = task_info.timeout
Garry Wangdce77572021-07-18 19:33:35 -0700161 self.multi_dut_trigger = task_info.multi_dut_trigger
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700162
163 if task_info.lc_branches:
164 self.launch_control_branches = [
165 t.strip() for t in task_info.lc_branches.split(',')]
166 else:
167 self.launch_control_branches = []
168
169 if task_info.lc_targets:
170 self.launch_control_targets = [
171 t.strip() for t in task_info.lc_targets.split(',')]
172 else:
173 self.launch_control_targets = []
174
175 if task_info.boards:
176 self.boards = [t.strip() for t in task_info.boards.split(',')]
177 else:
178 self.boards = []
179
Po-Hsien Wang6d589732018-05-15 17:19:34 -0700180 if task_info.exclude_boards:
181 self.exclude_boards = [
182 t.strip() for t in task_info.exclude_boards.split(',')]
183 else:
184 self.exclude_boards = []
185
Xixuan Wu89897182019-01-03 15:28:01 -0800186 if task_info.models:
187 self.models = [t.strip() for t in task_info.models.split(',')]
188 else:
189 self.models = []
190
191 if task_info.exclude_models:
192 self.exclude_models = [
193 t.strip() for t in task_info.exclude_models.split(',')]
194 else:
195 self.exclude_models = []
196
C Shapiro09108252019-08-01 14:52:52 -0500197 self.any_model = task_info.any_model
198
Po-Hsien Wangdd833072018-08-16 18:09:20 -0700199 if task_info.board_families:
200 # Finetune the allowed boards list with board_families & boards.
201 families = [family.strip()
202 for family in task_info.board_families.split(',')]
203 for family in families:
204 self.boards += board_family_config.get(family, [])
Xixuan Wu89897182019-01-03 15:28:01 -0800205
Po-Hsien Wangdd833072018-08-16 18:09:20 -0700206 if task_info.exclude_board_families:
207 # Finetune the disallowed boards list with exclude_board_families
208 # & exclude_boards.
209 families = [family.strip()
210 for family in task_info.exclude_board_families.split(',')]
211 for family in families:
212 self.exclude_boards += board_family_config.get(family, [])
213
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700214 if tot is None:
215 self.tot_manager = tot_manager.TotMilestoneManager()
216 else:
217 self.tot_manager = tot
218
Xinan Linc8647112020-02-04 16:45:56 -0800219 self.dimensions = task_info.dimensions
220
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700221 self._set_spec_compare_info()
Xinan Lin33937d62020-04-14 14:41:23 -0700222 # Sanity test does not have to upload metrics.
223 if not is_sanity:
224 self.job_section = analytics.ScheduleJobSection(task_info)
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700225
Garry Wangdce77572021-07-18 19:33:35 -0700226 self.is_multi_dut_testing = False
227 if task_info.multi_dut_boards:
228 self.is_multi_dut_testing = True
229 self.multi_dut_boards = [
230 t.strip().split(';') for t in task_info.multi_dut_boards.split(',')]
231 else:
232 self.multi_dut_boards = []
233
234 if task_info.multi_dut_models:
235 self.is_multi_dut_testing = True
236 self.multi_dut_models = [
237 t.strip().split(';') for t in task_info.multi_dut_models.split(',')]
238 else:
239 self.multi_dut_models = []
240
Xinan Lin028f9582019-12-11 10:55:33 -0800241 def schedule(self, launch_control_builds, cros_builds_tuple,
242 firmware_builds, configs):
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700243 """Schedule the task by its settings.
244
245 Args:
246 launch_control_builds: the build dict for Android boards, see
247 return value of |get_launch_control_builds|.
Craig Bergstrom58263d32018-04-26 14:11:35 -0600248 cros_builds_tuple: the two-tuple of build dicts for ChromeOS boards,
249 see return value of |get_cros_builds|.
Xinan Lin028f9582019-12-11 10:55:33 -0800250 firmware_builds: a dict of firmware artifact, see return value of
251 |base_event.get_firmware_builds|.
Xixuan Wuf4a4c882019-03-15 14:48:26 -0700252 configs: a config_reader.Configs object.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700253
254 Raises:
255 SchedulingError: if tasks that should be scheduled fail to schedule.
Xixuan Wu5451a662017-10-17 10:57:40 -0700256
257 Returns:
Jacob Kopczynski79d00102018-07-13 15:37:03 -0700258 A boolean indicator; true if there were any suites related to this
259 task which got pushed into the suites queue.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700260 """
Xixuan Wuf4a4c882019-03-15 14:48:26 -0700261 assert configs.lab_config is not None
Xixuan Wu5451a662017-10-17 10:57:40 -0700262 self.is_pushed = False
263
Craig Bergstrom58263d32018-04-26 14:11:35 -0600264 branch_builds, relaxed_builds = cros_builds_tuple
265 builds_dict = branch_builds
266 if self.only_hwtest_sanity_required:
Xinan Linae7d6372019-09-12 14:42:10 -0700267 builds_dict = _split_unibuilds(relaxed_builds, configs)
Craig Bergstrom58263d32018-04-26 14:11:35 -0600268
Xinan Lindf0698a2020-02-05 22:38:11 -0800269 # Record all target boards and models into job section.
270 lab_config = configs.lab_config
271 models_by_board = lab_config.get_cros_model_map() if lab_config else {}
272 boards = self.boards if self.boards else lab_config.get_cros_board_list()
273 for b in boards:
274 if self.exclude_boards and b in self.exclude_boards:
275 continue
276 self.job_section.add_board(b)
277 models = self.models or models_by_board.get(b, [])
278 for m in models:
279 if m and '%s_%s' % (b, m) not in self.exclude_models:
280 self.job_section.add_model(m)
281
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700282 logging.info('######## Scheduling task %s ########', self.name)
283 if self.os_type == build_lib.OS_TYPE_CROS:
Craig Bergstrom58263d32018-04-26 14:11:35 -0600284 if not builds_dict:
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700285 logging.info('No CrOS build to run, skip running.')
286 else:
Xinan Lin028f9582019-12-11 10:55:33 -0800287 self._schedule_cros_builds(builds_dict, firmware_builds, configs)
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700288 else:
289 if not launch_control_builds:
290 logging.info('No Android build to run, skip running.')
291 else:
292 self._schedule_launch_control_builds(launch_control_builds)
Xinan Lindf0698a2020-02-05 22:38:11 -0800293 upload_result = False
294 try:
295 upload_result = self.job_section.upload()
296 # For any exceptions from BQ, only log it and move on.
297 except Exception as e: #pylint: disable=broad-except
298 logging.exception(str(e))
299 if not upload_result:
300 logging.warning('Failed to insert row: %r', self.job_section)
Xixuan Wu5451a662017-10-17 10:57:40 -0700301 return self.is_pushed
302
Garry Wangdce77572021-07-18 19:33:35 -0700303 def schedule_multi_duts(self, cros_builds_tuple,
304 daily_cros_builds_tuple, configs):
305 """Schedule the multi-DUTs task by its settings.
306
307 Args:
308 cros_builds_tuple: the two-tuple of build dicts for ChromeOS boards,
309 see return value of |get_cros_builds|.
310 daily_cros_builds_tuple: Same as cros_builds_tuple, but contains
311 build info of last 24 hours.
312 configs: a config_reader.Configs object.
313
314 Raises:
315 SchedulingError: if tasks that should be scheduled fail to schedule.
316
317 Returns:
318 A boolean indicator; true if there were any suites related to this
319 task which got pushed into the suites queue.
320 """
321 assert configs.lab_config is not None
322 # For multi-DUTs We allow either request by board or request by model,
323 # but not both.
324 if self.multi_dut_boards and self.multi_dut_models:
325 logging.error('Both board and model presented in request but expecting'
326 'only one of them present at a time.')
327 return False
328 self.is_pushed = False
329
330 branch_builds, relaxed_builds = cros_builds_tuple
331 daily_branch_builds, daily_relaxed_builds = daily_cros_builds_tuple
332 builds_dict = branch_builds
333 daily_builds_dict = daily_branch_builds
334 if self.only_hwtest_sanity_required:
335 builds_dict = _split_unibuilds(relaxed_builds, configs)
336 # We don't need to expand build info to model level as
337 # secondary DUTs will have their builds determined by
338 # their board name.
339 daily_builds_dict = daily_relaxed_builds
340
341 # Record multi-DUTs testing only for primary boards as we're
342 # going to have a replacement for SS and analytics layer soon.
343 build_targets_dict = self._get_multi_duts_build_targets_dict(configs)
344 model_set = set()
345 for board, groups in build_targets_dict.items():
346 self.job_section.add_board(board)
347 for group in groups:
348 if group[0].model:
349 model_set.add(group[0].model)
350 for model in model_set:
351 if self.exclude_models and model not in self.exclude_models:
352 self.job_section.add_model(model)
353
354 logging.info('######## Scheduling task %s ########', self.name)
355 if self.os_type != build_lib.OS_TYPE_CROS:
356 logging.info('Multi-DUTs testing only support cros currently.')
357 return False
358 if not builds_dict:
359 logging.info('No CrOS build to run, skip running.')
360 return False
361 if not daily_builds_dict:
362 logging.info('No daily CrOS build for secondary DUTs to run,'
363 ' skip running.')
364 return False
365 self._schedule_multi_duts_cros_builds(builds_dict,
366 daily_builds_dict, configs)
367
368 upload_result = False
369 try:
370 upload_result = self.job_section.upload()
371 # For any exceptions from BQ, only log it and move on.
372 except Exception as e: #pylint: disable=broad-except
373 logging.exception(str(e))
374 if not upload_result:
375 logging.warning('Failed to insert row: %r', self.job_section)
376 return self.is_pushed
377
378 def _model_to_board_dict(self, models_by_board):
379 """Build a model to board dict based on model by boards dict."""
380 d = {}
381 for board, models in models_by_board.items():
382 for model in models:
383 d[model] = board
384 return d
385
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700386 def _set_spec_compare_info(self):
387 """Set branch spec compare info for task for further check."""
388 self._bare_branches = []
389 self._version_equal_constraint = False
390 self._version_gte_constraint = False
391 self._version_lte_constraint = False
392
393 if not self.branch_specs:
394 # Any milestone is OK.
395 self._numeric_constraint = version.LooseVersion('0')
396 else:
397 self._numeric_constraint = None
398 for spec in self.branch_specs:
399 if 'tot' in spec.lower():
400 # Convert spec >=tot-1 to >=RXX.
401 tot_str = spec[spec.index('tot'):]
402 spec = spec.replace(
403 tot_str, self.tot_manager.convert_tot_spec(tot_str))
404
405 if spec.startswith('>='):
406 self._numeric_constraint = version.LooseVersion(
407 spec.lstrip('>=R'))
408 self._version_gte_constraint = True
409 elif spec.startswith('<='):
410 self._numeric_constraint = version.LooseVersion(
411 spec.lstrip('<=R'))
412 self._version_lte_constraint = True
413 elif spec.startswith('=='):
414 self._version_equal_constraint = True
415 self._numeric_constraint = version.LooseVersion(
416 spec.lstrip('==R'))
417 else:
418 self._bare_branches.append(spec)
419
420 def _fits_spec(self, branch):
421 """Check if a branch is deemed OK by this task's branch specs.
422
423 Will return whether a branch 'fits' the specifications stored in this task.
424
425 Examples:
426 Assuming tot=R40
427 t = Task('Name', 'suite', ['factory', '>=tot-1'])
428 t._fits_spec('factory') # True
429 t._fits_spec('40') # True
430 t._fits_spec('38') # False
431 t._fits_spec('firmware') # False
432
433 Args:
434 branch: the branch to check.
435
436 Returns:
437 True if branch 'fits' with stored specs, False otherwise.
438 """
439 if branch in build_lib.BARE_BRANCHES:
440 return branch in self._bare_branches
441
442 if self._numeric_constraint:
443 if self._version_equal_constraint:
444 return version.LooseVersion(branch) == self._numeric_constraint
445 elif self._version_gte_constraint:
446 return version.LooseVersion(branch) >= self._numeric_constraint
447 elif self._version_lte_constraint:
448 return version.LooseVersion(branch) <= self._numeric_constraint
449 else:
450 return version.LooseVersion(branch) >= self._numeric_constraint
451 else:
452 return False
453
Xinan Lin028f9582019-12-11 10:55:33 -0800454 def _get_firmware_build(self, spec, board, firmware_build_dict, lab_config):
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700455 """Get the firmware build name to test with ChromeOS build.
456
457 Args:
458 spec: a string build spec for RO or RW firmware, eg. firmware,
459 cros. For RO firmware, the value can also be released_ro_X.
460 board: a string board against which this task will run suite job.
Xinan Lin028f9582019-12-11 10:55:33 -0800461 firmware_build_dict: a dict of firmware artifacts, see return value of
462 |base_event.get_firmware_builds|.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700463 lab_config: a config.LabConfig object, to read lab config file.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700464
465 Returns:
466 A string firmware build name.
467
468 Raises:
469 ValueError: if failing to get firmware from lab config file;
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700470 """
471 if not spec or spec == 'stable':
472 # TODO(crbug.com/577316): Query stable RO firmware.
473 logging.debug('%s RO firmware build is not supported.', spec)
474 return None
475
476 try:
Dhanya Ganeshf6014b72020-08-04 22:33:28 +0000477 if firmware_build_dict:
Xinan Lin028f9582019-12-11 10:55:33 -0800478 return firmware_build_dict.get((spec, board), None)
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700479 else:
Xinan Lin028f9582019-12-11 10:55:33 -0800480 return None
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700481 except ValueError as e:
482 logging.warning('Failed to get firmware from lab config file'
483 'for spec %s, board %s: %s', spec, board, str(e))
484 return None
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700485
C Shapiro7f24a002017-12-05 14:25:09 -0700486 def _push_suite(
487 self,
Xinan Lindf0698a2020-02-05 22:38:11 -0800488 task_id=None,
C Shapiro7f24a002017-12-05 14:25:09 -0700489 board=None,
490 model=None,
491 cros_build=None,
492 firmware_rw_build=None,
493 firmware_ro_build=None,
494 test_source_build=None,
495 launch_control_build=None,
Garry Wangdce77572021-07-18 19:33:35 -0700496 run_prod_code=False,
497 secondary_targets=None):
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700498 """Schedule suite job for the task by pushing suites to SuiteQueue.
499
500 Args:
Xinan Lindf0698a2020-02-05 22:38:11 -0800501 task_id: the id to track this task in exectuion.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700502 board: the board against which this suite job run.
C Shapiro7f24a002017-12-05 14:25:09 -0700503 model: the model name for unibuild.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700504 cros_build: the CrOS build of this suite job.
505 firmware_rw_build: Firmware RW build to run this suite job with.
506 firmware_ro_build: Firmware RO build to run this suite job with.
507 test_source_build: Test source build, used for server-side
508 packaging of this suite job.
509 launch_control_build: the launch control build of this suite job.
510 run_prod_code: If True, the suite will run the test code that lives
511 in prod aka the test code currently on the lab servers. If
512 False, the control files and test code for this suite run will
513 be retrieved from the build artifacts. Default is False.
Garry Wangdce77572021-07-18 19:33:35 -0700514 secondary_targets: A list of BuildTarget namedtuple to represent
515 required secondary DUTs info in this suite job run.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700516 """
517 android_build = None
518 testbed_build = None
519
520 if self.testbed_dut_count:
521 launch_control_build = '%s#%d' % (launch_control_build,
522 self.testbed_dut_count)
523 test_source_build = launch_control_build
524 board = '%s-%d' % (board, self.testbed_dut_count)
525
526 if launch_control_build:
527 if not self.testbed_dut_count:
528 android_build = launch_control_build
529 else:
530 testbed_build = launch_control_build
531
532 suite_job_parameters = {
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700533 build_lib.BuildVersionKey.ANDROID_BUILD_VERSION: android_build,
Jacob Kopczynskic1d0f5c2020-08-09 10:41:32 -0700534 build_lib.BuildVersionKey.CROS_VERSION: cros_build,
535 build_lib.BuildVersionKey.FW_RO_VERSION: firmware_ro_build,
536 build_lib.BuildVersionKey.FW_RW_VERSION: firmware_rw_build,
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700537 build_lib.BuildVersionKey.TESTBED_BUILD_VERSION: testbed_build,
Taylor Clark8b06a832021-02-16 21:45:42 +0000538 'analytics_name': self.analytics_name,
Jacob Kopczynskic1d0f5c2020-08-09 10:41:32 -0700539 'board': board,
540 'dimensions': self.dimensions,
541 'force': self.force,
542 'job_retry': self.job_retry,
543 'max_runtime_mins': _JOB_MAX_RUNTIME_MINS_DEFAULT,
544 'model': model,
545 'name': self.name,
546 'no_delay': self.no_delay,
547 'no_wait_for_results': not self.job_retry,
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700548 'num': self.num,
549 'pool': self.pool,
550 'priority': self.priority,
Jacob Kopczynskic1d0f5c2020-08-09 10:41:32 -0700551 'qs_account': self.qs_account,
552 'run_prod_code': run_prod_code,
Garry Wangdce77572021-07-18 19:33:35 -0700553 'secondary_targets': secondary_targets,
Jacob Kopczynskic1d0f5c2020-08-09 10:41:32 -0700554 'suite': self.suite,
555 'task_id': task_id,
556 'test_source_build': test_source_build,
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700557 'timeout': self.timeout,
558 'timeout_mins': _JOB_MAX_RUNTIME_MINS_DEFAULT,
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700559 }
560
Xinan Lin9e4917d2019-11-04 10:58:47 -0800561 task_executor.push(task_executor.SUITES_QUEUE,
562 tag=self.suite,
563 **suite_job_parameters)
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700564 logging.info('Pushing task %r into taskqueue', suite_job_parameters)
Xixuan Wu5451a662017-10-17 10:57:40 -0700565 self.is_pushed = True
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700566
Xinan Lin028f9582019-12-11 10:55:33 -0800567 def _schedule_cros_builds(self, build_dict, firmware_build_dict, configs):
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700568 """Schedule tasks with branch builds.
569
570 Args:
Craig Bergstrom58263d32018-04-26 14:11:35 -0600571 build_dict: the build dict for ChromeOS boards, see return
Xinan Linea1efcb2019-12-30 23:46:42 -0800572 value of |build_lib.get_cros_builds|.
Xinan Lin028f9582019-12-11 10:55:33 -0800573 firmware_build_dict: a dict of firmware artifact, see return value of
574 |base_event.get_firmware_builds|.
Xixuan Wuf4a4c882019-03-15 14:48:26 -0700575 configs: A config_reader.Configs object.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700576 """
Xixuan Wuf4a4c882019-03-15 14:48:26 -0700577 lab_config = configs.lab_config
C Shapiro7f24a002017-12-05 14:25:09 -0700578 models_by_board = lab_config.get_cros_model_map() if lab_config else {}
C Shapiro09108252019-08-01 14:52:52 -0500579 model_agnostic_cros_builds = set()
Xixuan Wu8d2f2862018-08-28 16:48:04 -0700580 for (board, passed_model, build_type,
Craig Bergstrom58263d32018-04-26 14:11:35 -0600581 milestone), manifest in build_dict.iteritems():
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700582 cros_build = str(build_lib.CrOSBuild(board, build_type, milestone,
583 manifest))
584 logging.info('Running %s on %s', self.name, cros_build)
Po-Hsien Wang6d589732018-05-15 17:19:34 -0700585 if self.exclude_boards and board in self.exclude_boards:
586 logging.debug('Board %s is in excluded board list: %s',
587 board, self.exclude_boards)
588 continue
Xixuan Wu8d2f2862018-08-28 16:48:04 -0700589
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700590 if self.boards and board not in self.boards:
591 logging.debug('Board %s is not in supported board list: %s',
592 board, self.boards)
593 continue
594
595 # Check the fitness of the build's branch for task
596 branch_build_spec = _pick_branch(build_type, milestone)
597 if not self._fits_spec(branch_build_spec):
Xinan Lindf0698a2020-02-05 22:38:11 -0800598 msg = ("branch_build spec %s doesn't fit this task's "
599 "requirement: %s") % (branch_build_spec,
600 ",".join(self.branch_specs))
601 logging.debug(msg)
602 self.job_section.add_schedule_job(board, passed_model, msg=msg)
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700603 continue
604
Xinan Lindf0698a2020-02-05 22:38:11 -0800605 # Record this build as it matches both board and branch specs.
606 if self.only_hwtest_sanity_required:
607 self.job_section.add_matched_relax_build(
608 board, build_type, milestone, manifest)
609 else:
610 self.job_section.add_matched_build(
611 board, build_type, milestone, manifest)
612
Sean McAllister7378b692021-06-03 15:44:22 -0600613 # check that we only got one spec for ro firmware
614 if all([self.firmware_ro_version, self.firmware_ro_build_spec]):
615 logging.error(
616 "Exactly one of firmware_ro_version or firmware_ro_build_spec " \
617 "allowed, got: %s and %s" % (
618 self.firmware_ro_version,
619 self.firmware_ro_build_spec,
620 ),
621 )
622 continue
623
624 # check that we only got one spec for rw firmware
625 if all([self.firmware_rw_version, self.firmware_rw_build_spec]):
626 logging.error(
627 "Exactly one of firmware_rw_version or firmware_rw_build_spec " \
628 "allowed, got: %s and %s" % (
629 self.firmware_rw_version,
630 self.firmware_rw_build_spec,
631 ),
632 )
633 continue
634
635 # resolve ro firmware version to provision
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700636 firmware_ro_build = None
Brigit Rossbachbb080912020-11-18 13:52:17 -0700637 if self.firmware_ro_version:
638 firmware_ro_build = self.firmware_ro_version
Sean McAllister7378b692021-06-03 15:44:22 -0600639 elif self.firmware_ro_build_spec:
640 firmware_ro_build = self._get_firmware_build(
641 self.firmware_ro_build_spec, board, firmware_build_dict, lab_config)
Brigit Rossbachbb080912020-11-18 13:52:17 -0700642
Sean McAllister7378b692021-06-03 15:44:22 -0600643 if not firmware_ro_build:
644 msg = 'No RO firmware build to run, skip running'
645 logging.debug(msg)
646 self.job_section.add_schedule_job(board, passed_model, msg=msg)
647 continue
648
649 # resolve rw firmware version to provision
650 firmware_rw_build = None
Brigit Rossbachbb080912020-11-18 13:52:17 -0700651 if self.firmware_rw_version:
652 firmware_rw_build = self.firmware_rw_version
Sean McAllister7378b692021-06-03 15:44:22 -0600653 elif self.firmware_rw_build_spec:
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700654 firmware_rw_build = self._get_firmware_build(
Sean McAllister7378b692021-06-03 15:44:22 -0600655 self.firmware_rw_build_spec, board, firmware_build_dict, lab_config)
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700656
Sean McAllister7378b692021-06-03 15:44:22 -0600657 if not firmware_rw_build:
658 msg = 'No RW firmware build to run, skip running'
659 logging.debug(msg)
660 self.job_section.add_schedule_job(board, passed_model, msg=msg)
661 continue
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700662
663 if self.test_source == build_lib.BuildType.FIRMWARE_RW:
664 test_source_build = firmware_rw_build
665 else:
Jacob Kopczynski79d00102018-07-13 15:37:03 -0700666 # Default test source build to CrOS build if it's not specified.
667 # Past versions chose based on run_prod_code, but we no longer respect
668 # that option and scheduler settings should always set it to False.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700669 test_source_build = cros_build
670
Xinan Lindf0698a2020-02-05 22:38:11 -0800671 # Record the matched firwmare build.
672 if firmware_rw_build:
673 self.job_section.add_matched_fw_build(
674 board,
675 self.firmware_rw_build_spec,
676 firmware_rw_build,
677 read_only=False)
678 if firmware_ro_build:
679 self.job_section.add_matched_fw_build(
680 board,
681 self.firmware_ro_build_spec,
682 firmware_ro_build,
683 read_only=True)
684
Xinan Lin6668e0f2020-05-29 10:02:57 -0700685 # Board above is used as build target to control the CrOS image.
686 # The following part is to assign models for lab boards, where
687 # the suffix should be removed.
Prathmesh Prabhu074b8d42020-08-13 17:17:33 +0000688 hwtest_board = build_lib.reshape_board(board)
689
Xinan Lin6668e0f2020-05-29 10:02:57 -0700690 models = models_by_board.get(hwtest_board, [None])
C Shapiro7f24a002017-12-05 14:25:09 -0700691
Harpreet Grewalbbbb7de2019-02-05 19:35:03 +0000692 for model in models:
693 if ((passed_model is not None and model == passed_model) or
694 passed_model is None):
Xinan Lin6668e0f2020-05-29 10:02:57 -0700695 full_model_name = '%s_%s' % (hwtest_board, model)
Xixuan Wua41efa22019-05-17 14:28:04 -0700696 # Respect exclude first.
697 if self.exclude_models and full_model_name in self.exclude_models:
698 logging.debug("Skip model %s as it's in exclude model list %s",
699 model, self.exclude_models)
700 continue
701
702 if self.models and full_model_name not in self.models:
703 logging.debug("Skip model %s as it's not in support model list %s",
704 model, self.models)
705 continue
706
C Shapiro09108252019-08-01 14:52:52 -0500707 explicit_model = model
708
709 if self.any_model:
Xinan Lin3ba18a02019-08-13 15:44:55 -0700710 explicit_model = None
C Shapiro09108252019-08-01 14:52:52 -0500711 unique_build = str(cros_build)
712 if unique_build in model_agnostic_cros_builds:
713 # Skip since we've already run with no explicit model set.
Xinan Lindf0698a2020-02-05 22:38:11 -0800714 msg = "Skip model %s as any_model enabled for this job." % model
715 self.job_section.add_schedule_job(board, model, msg=msg)
C Shapiro09108252019-08-01 14:52:52 -0500716 continue
717 model_agnostic_cros_builds.add(unique_build)
718
Xinan Lindf0698a2020-02-05 22:38:11 -0800719 task_id = str(uuid.uuid1())
Harpreet Grewalbbbb7de2019-02-05 19:35:03 +0000720 self._push_suite(
Xinan Lindf0698a2020-02-05 22:38:11 -0800721 task_id=task_id,
Xixuan Wub4b2f412019-05-03 11:22:31 -0700722 board=hwtest_board,
C Shapiro09108252019-08-01 14:52:52 -0500723 model=explicit_model,
Harpreet Grewalbbbb7de2019-02-05 19:35:03 +0000724 cros_build=cros_build,
725 firmware_rw_build=firmware_rw_build,
726 firmware_ro_build=firmware_ro_build,
Xinan Lin0550f492020-01-21 16:25:53 -0800727 test_source_build=test_source_build)
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700728
Xinan Lin6668e0f2020-05-29 10:02:57 -0700729 # Analytics table stores the build target instead of the lab board.
Xinan Lin0673a182020-04-14 15:09:35 -0700730 self.job_section.add_schedule_job(
731 board, explicit_model, task_id=task_id)
Xinan Lindf0698a2020-02-05 22:38:11 -0800732
Garry Wangdce77572021-07-18 19:33:35 -0700733 def _schedule_multi_duts_cros_builds(self, build_dict,
734 daily_build_dict, configs):
735 """Schedule multi-DUTs tasks with branch builds.
736
737 Args:
738 build_dict: the build dict for ChromeOS boards, see return
739 value of |build_lib.get_cros_builds|.
740 daily_build_dict: Same as build_dict, but contains build info
741 of last 24 hours.
742 configs: A config_reader.Configs object.
743 """
744 build_targets_dict = self._get_multi_duts_build_targets_dict(configs)
745 model_agnostic_builds = set()
746 for (board, passed_model, build_type,
747 milestone), manifest in build_dict.iteritems():
748 cros_build = str(build_lib.CrOSBuild(board, build_type, milestone,
749 manifest))
750 logging.info('Running %s on %s', self.name, cros_build)
751 if board not in build_targets_dict.keys():
752 logging.debug('Board %s is not in primary board list: %s',
753 board, build_targets_dict.keys())
754 continue
755
756 # Check the fitness of the build's branch for task
757 branch_build_spec = _pick_branch(build_type, milestone)
758 if not self._fits_spec(branch_build_spec):
759 msg = ("branch_build spec %s doesn't fit this task's "
760 "requirement: %s") % (branch_build_spec,
761 ",".join(self.branch_specs))
762 logging.debug(msg)
763 self.job_section.add_schedule_job(board, passed_model, msg=msg)
764 continue
765
766 # Record this build as it matches both board and branch specs.
767 if self.only_hwtest_sanity_required:
768 self.job_section.add_matched_relax_build(
769 board, build_type, milestone, manifest)
770 else:
771 self.job_section.add_matched_build(
772 board, build_type, milestone, manifest)
773
774 # Board above is used as build target to control the CrOS image.
775 # The following part is to assign models for lab boards, where
776 # the suffix should be removed.
777 hwtest_board = build_lib.reshape_board(board)
778 for group in build_targets_dict.get(board):
779 joined_board_strings = '_'.join(dut.board for dut in group)
780 build_tag = '%s_%s' % (joined_board_strings, cros_build)
781 primary_dut = group[0]
782 if primary_dut.model:
783 if primary_dut.model != passed_model:
784 # Explict model specified but not match, so skip to next.
785 continue
786 elif build_tag in model_agnostic_builds:
787 msg = ("Skip model %s as any_model enabled for this job."
788 % passed_model)
789 self.job_section.add_schedule_job(board, passed_model, msg=msg)
790 continue
791
792 # Determine cros builds for secondary DUTs from daily build dict.
793 secondary_targets = []
794 for s_dut in group[1:]:
795 s_key = (s_dut.board, None, build_type, milestone)
796 s_manifest = daily_build_dict.get(s_key, '')
797 if s_manifest:
798 s_cros_build = str(build_lib.CrOSBuild(
799 s_dut.board, build_type, milestone, s_manifest))
800 s_hwtest_board = build_lib.reshape_board(s_dut.board)
801 secondary_targets.append(
802 BuildTarget(s_hwtest_board, s_dut.model, s_cros_build))
803 # Check if we get cros build for all secondary DUTs.
804 if len(secondary_targets) != len(group[1:]):
805 logging.info('Cannot determine cros version for all secondary'
806 ' DUTs, skip.')
807 continue
808
809 # Schedule task.
810 model_agnostic_builds.add(build_tag)
811 task_id = str(uuid.uuid1())
812 self._push_suite(
813 task_id = task_id,
814 board=hwtest_board,
815 model=primary_dut.model,
816 cros_build=cros_build,
817 test_source_build=cros_build,
818 secondary_targets=secondary_targets
819 )
820
821 # Analytics table stores the build target instead of the lab board.
822 self.job_section.add_schedule_job(
823 board, primary_dut.model, task_id=task_id)
824
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700825 def _schedule_launch_control_builds(self, launch_control_builds):
826 """Schedule tasks with launch control builds.
827
828 Args:
829 launch_control_builds: the build dict for Android boards.
830 """
831 for board, launch_control_build in launch_control_builds.iteritems():
832 logging.debug('Running %s on %s', self.name, board)
Po-Hsien Wang6d589732018-05-15 17:19:34 -0700833 if self.exclude_boards and board in self.exclude_boards:
834 logging.debug('Board %s is in excluded board list: %s',
835 board, self.exclude_boards)
836 continue
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700837 if self.boards and board not in self.boards:
838 logging.debug('Board %s is not in supported board list: %s',
839 board, self.boards)
840 continue
841
842 for android_build in launch_control_build:
843 if not any([branch in android_build
844 for branch in self.launch_control_branches]):
845 logging.debug('Branch %s is not required to run for task '
846 '%s', android_build, self.name)
847 continue
848
849 self._push_suite(board=board,
850 test_source_build=android_build,
851 launch_control_build=android_build)
852
Garry Wangdce77572021-07-18 19:33:35 -0700853 def _get_multi_duts_build_targets_dict(self, configs):
854 """Generate a dict contains all build targets info based on
855 self.multi_dut_boards or self.multi_dut_models.
856
857 Args:
858 configs: A config_reader.Configs object.
859
860 Returns:
861 A dict where key is a cros board, and value is a list that represents
862 group of DUTs where primary DUT's board equal to key. Each group of
863 DUTs are represented by a list of BuildTarget. Example:
864 {
865 'coral': [
866 [
867 BuildTarget(board='coral', model='babytiger', cros_build=None),
868 BuildTarget(board='eve', model=None, cros_build=None),
869 BuildTarget(board='nami', model='None', cros_build=None)
870 ],
871 [
872 BuildTarget(board='coral', model='blacktiplte', cros_build=None),
873 BuildTarget(board='octopus', model=None, cros_build=None),
874 BuildTarget(board='nami', model=None, cros_build=None)],
875 ]
876 ]
877 }
878 """
879 lab_config = configs.lab_config
880 build_targets = defaultdict(lambda: [])
881 models_by_board = lab_config.get_cros_model_map() if lab_config else {}
882 model_to_board_dict = self._model_to_board_dict(models_by_board)
883 # Handle the case when multi-DUTs testing requested by board.
884 for group in self.multi_dut_boards:
885 primary_board = group[0]
886 if self.any_model:
887 new_group = [BuildTarget(board, None, None) for board in group]
888 build_targets[primary_board].append(new_group)
889 else:
890 # If any_model disbled, we need to have each model from primary_board
891 # family pair with requested secondary devices.
892 # Board above is used as build target to control the CrOS image.
893 # The following part is to assign models for lab boards, where
894 # the suffix should be removed.
895 hwtest_board = build_lib.reshape_board(primary_board)
896 models = models_by_board.get(hwtest_board, [None])
897 for model in models:
898 if self.exclude_models and model in self.exclude_models:
899 logging.info('Model %s is in exclude list, skipping.' % model)
900 continue
901 new_group = []
902 new_group.append(BuildTarget(primary_board, model, None))
903 for board in group[1:]:
904 new_group.append(BuildTarget(board, None, None))
905 build_targets[primary_board].append(new_group)
906 # Handle the case when multi-DUTs testing requested by model.
907 for group in self.multi_dut_models:
908 boards = [model_to_board_dict.get(model, '') for model in group]
909 if '' in boards:
910 logging.info('Cannot find board name from one of requested model,'
911 ' skipping.')
912 continue
913 new_group = [BuildTarget(boards[i], group[i], None) for i in range(len(group))]
914 build_targets[boards[0]].append(new_group)
915 return build_targets
916
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700917
918def _pick_branch(build_type, milestone):
919 """Select branch based on build type.
920
921 If the build_type is a bare branch, return build_type as the build spec.
922 If the build_type is a normal CrOS branch, return milestone as the build
923 spec.
924
925 Args:
926 build_type: a string builder name, like 'release'.
927 milestone: a string milestone, like '55'.
928
929 Returns:
930 A string milestone if build_type represents CrOS build, otherwise
931 return build_type.
932 """
933 return build_type if build_type in build_lib.BARE_BRANCHES else milestone
Xinan Linae7d6372019-09-12 14:42:10 -0700934
935
936def _split_unibuilds(build_dict, configs):
937 """Split the uni-builds to all models under a board.
938
939 Args:
940 build_dict: the build dict for ChromeOS boards, see return
Xinan Linea1efcb2019-12-30 23:46:42 -0800941 value of |build_lib.get_cros_builds|.
Xinan Linae7d6372019-09-12 14:42:10 -0700942 configs: a config_reader.Configs object.
943
944 Returns:
945 A build dict.
946 """
947 models_by_board = configs.lab_config.get_cros_model_map()
948 if not models_by_board:
949 return build_dict
950 all_branch_build_dict = {}
951 for (board, model, config, milestone), platform in build_dict.iteritems():
952 uni_build_models = models_by_board.get(board)
953 if uni_build_models is not None and model is None:
954 for uni_build_model in uni_build_models:
955 model_key = (board, uni_build_model, config, milestone)
956 _add_build_dict(all_branch_build_dict, model_key, platform)
957 continue
958 build_key = (board, model, config, milestone)
959 _add_build_dict(all_branch_build_dict, build_key, platform)
960
961 return all_branch_build_dict
962
963
964def _add_build_dict(build_dict, key, value):
965 """A wrapper to add or update an item in build_dict."""
966 cur_manifest = build_dict.get(key)
967 if cur_manifest is None:
968 build_dict[key] = value
969 return
970 build_dict[key] = max(
971 [cur_manifest, value], key=version.LooseVersion)