blob: 617d63d20bdd05f5f494b7929776c6c2cf331333 [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 Wang111a26f2021-07-23 15:25:14 -07009from collections import 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
Garry Wang111a26f2021-07-23 15:25:14 -070018from multi_duts_lib import BuildTarget, convert_secondary_targets_to_string
19
Xixuan Wu0c76d5b2017-08-30 16:40:17 -070020# The max lifetime of a suite scheduled by suite scheduler
Dhanya Ganesha4ce38e2021-10-26 15:45:47 +000021_JOB_MAX_RUNTIME_MINS_DEFAULT = (39 * 60 + 30)
Xixuan Wu0c76d5b2017-08-30 16:40:17 -070022
Xixuan Wu0c76d5b2017-08-30 16:40:17 -070023
24class SchedulingError(Exception):
25 """Raised to indicate a failure in scheduling a task."""
26
27
28class Task(object):
29 """Represents an entry from the suite_scheduler config file.
30
31 Each entry from the suite_scheduler config file maps one-to-one to a
32 Task. Each instance has enough information to schedule itself.
33 """
34
Xinan Lin33937d62020-04-14 14:41:23 -070035 def __init__(self,
36 task_info,
37 board_family_config={},
38 tot=None,
39 is_sanity=False):
Xixuan Wu0c76d5b2017-08-30 16:40:17 -070040 """Initialize a task instance.
41
42 Args:
43 task_info: a config_reader.TaskInfo object, which includes:
44 name, name of this task, e.g. 'NightlyPower'
45 suite, the name of the suite to run, e.g. 'graphics_per-day'
46 branch_specs, a pre-vetted iterable of branch specifiers,
47 e.g. ['>=R18', 'factory']
48 pool, the pool of machines to schedule tasks. Default is None.
49 num, the number of devices to shard the test suite. It could
50 be an Integer or None. By default it's None.
Taylor Clark54427ce2021-02-18 21:59:27 +000051 analytics_name, the build rule name. Initially build rule or test rule
52 name was used in suite scheduler's dashboard for analytics. Later it
53 was expanded to the entire pipeline and we want to tag all requests
54 from Suite Scheduler with the rule name.
Po-Hsien Wangdd833072018-08-16 18:09:20 -070055 board_families, a common separated list of board family to run this
56 task on. Boards belong to one of the board family in this list
57 would be added to task_info.boards.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -070058 boards, a comma separated list of boards to run this task on. Default
Po-Hsien Wangdd833072018-08-16 18:09:20 -070059 is None, which allows this task to run on all boards. If same board
60 is specified in 'boards' and 'exclude_boards', we exclude this
61 board.
Xinan Linc8647112020-02-04 16:45:56 -080062 dimensions, a comma separated lists of labels. Each label is in
63 the form of 'key:value'.
Po-Hsien Wangdd833072018-08-16 18:09:20 -070064 exclude_board_families, a common separated list of board family not to
65 run task on. Boards belong to one of the board family in this list
66 would be added to task_info.exclude_boards.
Po-Hsien Wang6d589732018-05-15 17:19:34 -070067 exclude_boards, a comma separated list of boards not to run this task
68 on. Default is None, which allows this task to run on all boards.
Po-Hsien Wangdd833072018-08-16 18:09:20 -070069 If same board is specified in 'boards' and 'exclude_boards', we
70 exclude this board.
Xixuan Wu89897182019-01-03 15:28:01 -080071 models, a comma separated list of models to run this task on. Default
72 is None, which allows this task to run on all models. If same model
73 is specified in 'models' and 'exclude_models', we exclude this
74 model.
75 exclude_models, a comma separated list of models not to run this task
76 on. Default is None, which allows this task to run on all models.
C Shapiro09108252019-08-01 14:52:52 -050077 any_model, set to True to not pass the model parameter and allow
78 a test suite to run any/all models available for testing.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -070079 priority, the string name of a priority from constants.Priorities.
80 timeout, the max lifetime of the suite in hours.
81 cros_build_spec, spec used to determine the ChromeOS build to test
82 with a firmware build, e.g., tot, R41 etc.
83 firmware_rw_build_spec, spec used to determine the firmware RW build
84 test with a ChromeOS build.
85 firmware_ro_build_spec, spec used to determine the firmware RO build
86 test with a ChromeOS build.
Brigit Rossbachbb080912020-11-18 13:52:17 -070087 firmware_ro_version, pinned firmware RO version.
88 firmware_rw_version, pinned firmware RW version.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -070089 test_source, the source of test code when firmware will be updated in
90 the test. The value can be 'firmware_rw', 'firmware_ro' or 'cros'.
91 job_retry, set to True to enable job-level retry. Default is False.
Xixuan Wu80531932017-10-12 17:26:51 -070092 no_delay, set to True to raise the priority of this task in task.
93 force, set to True to schedule this suite no matter whether there's
94 duplicate jobs before.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -070095 queue, so the suite jobs can start running tests with no waiting.
96 hour, an integer specifying the hour that a nightly run should be
97 triggered, default is set to 21.
98 day, an integer specifying the day of a week that a weekly run should
99 be triggered, default is set to 5 (Saturday).
100 os_type, type of OS, e.g., cros, brillo, android. Default is cros.
101 The argument is required for android/brillo builds.
102 launch_control_branches, comma separated string of launch control
103 branches. The argument is required and only applicable for
104 android/brillo builds.
105 launch_control_targets, comma separated string of build targets for
106 launch control builds. The argument is required and only
107 applicable for android/brillo builds.
108 testbed_dut_count, number of duts to test when using a testbed.
Xinan Lin4757d6f2020-03-24 22:20:31 -0700109 qs_account, quota account for the unmanaged pool which has enabled
110 Quota Scheduler.
Garry Wangdce77572021-07-18 19:33:35 -0700111 multi_dut_boards, a common separated list of strings to specify
112 board family in a multi-DUTs testing. Each string contains two
113 or more boards that separated by semicolon, where the first board
114 will be treated as primary board while the rest are secondaries.
115 If this args is not None then multi_dut_models args will be
116 ignored as they cannot be used together.
117 multi_dut_models, a common separated list of strings to specify
118 models of DUTs expected in a multi-DUTs testing. Each string
119 contains two or more models that separated by semicolon, where the
120 first model will be treated as primary model while the rest are
121 secondaries.
122 multi_dut_trigger, a string to specify how should we trigger a
123 multi-DUTs testing. E.g. 'primary': when the primary board has
124 a new build, or 'all': when primary and all secondary has a new
125 build.
Jacob Kopczynskic54520f2020-08-07 13:20:12 -0700126
Xixuan Wu83118dd2018-08-27 12:11:35 -0700127 board_family_config: A board family dictionary mapping board_family name
128 to its corresponding boards.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700129 tot: The tot manager for checking ToT. If it's None, a new tot_manager
130 instance will be initialized.
Xinan Lin33937d62020-04-14 14:41:23 -0700131 is_sanity: A boolean; true if we are running in sanity env.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700132 """
Xixuan Wu5451a662017-10-17 10:57:40 -0700133 # Indicate whether there're suites get pushed into taskqueue for this task.
134 self.is_pushed = False
135
Taylor Clark8b06a832021-02-16 21:45:42 +0000136 self.analytics_name = task_info.analytics_name
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700137 self.branch_specs = task_info.branch_specs
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700138 self.cros_build_spec = task_info.cros_build_spec
Xixuan Wu008ee832017-10-12 16:59:34 -0700139 self.day = task_info.day
Jacob Kopczynskic1d0f5c2020-08-09 10:41:32 -0700140 self.firmware_ro_build_spec = task_info.firmware_ro_build_spec
141 self.firmware_rw_build_spec = task_info.firmware_rw_build_spec
Brigit Rossbachbb080912020-11-18 13:52:17 -0700142 self.firmware_ro_version = task_info.firmware_ro_version
143 self.firmware_rw_version = task_info.firmware_rw_version
Jacob Kopczynskic1d0f5c2020-08-09 10:41:32 -0700144 self.force = task_info.force
145 self.frontdoor = task_info.frontdoor
146 self.hour = task_info.hour
147 self.job_retry = task_info.job_retry
148 self.name = task_info.name
149 self.no_delay = task_info.no_delay
150 self.num = task_info.num
Craig Bergstrom58263d32018-04-26 14:11:35 -0600151 self.only_hwtest_sanity_required = task_info.only_hwtest_sanity_required
Jacob Kopczynskic1d0f5c2020-08-09 10:41:32 -0700152 self.os_type = task_info.os_type
153 self.pool = task_info.pool
154 self.priority = task_info.priority
Xinan Lin4757d6f2020-03-24 22:20:31 -0700155 self.qs_account = task_info.qs_account
Jacob Kopczynskic1d0f5c2020-08-09 10:41:32 -0700156 self.suite = task_info.suite
157 self.test_source = task_info.test_source
158 self.testbed_dut_count = task_info.testbed_dut_count
159 self.timeout = task_info.timeout
Garry Wangdce77572021-07-18 19:33:35 -0700160 self.multi_dut_trigger = task_info.multi_dut_trigger
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700161
162 if task_info.lc_branches:
163 self.launch_control_branches = [
164 t.strip() for t in task_info.lc_branches.split(',')]
165 else:
166 self.launch_control_branches = []
167
168 if task_info.lc_targets:
169 self.launch_control_targets = [
170 t.strip() for t in task_info.lc_targets.split(',')]
171 else:
172 self.launch_control_targets = []
173
174 if task_info.boards:
175 self.boards = [t.strip() for t in task_info.boards.split(',')]
176 else:
177 self.boards = []
178
Po-Hsien Wang6d589732018-05-15 17:19:34 -0700179 if task_info.exclude_boards:
180 self.exclude_boards = [
181 t.strip() for t in task_info.exclude_boards.split(',')]
182 else:
183 self.exclude_boards = []
184
Xixuan Wu89897182019-01-03 15:28:01 -0800185 if task_info.models:
186 self.models = [t.strip() for t in task_info.models.split(',')]
187 else:
188 self.models = []
189
190 if task_info.exclude_models:
191 self.exclude_models = [
192 t.strip() for t in task_info.exclude_models.split(',')]
193 else:
194 self.exclude_models = []
195
C Shapiro09108252019-08-01 14:52:52 -0500196 self.any_model = task_info.any_model
197
Po-Hsien Wangdd833072018-08-16 18:09:20 -0700198 if task_info.board_families:
199 # Finetune the allowed boards list with board_families & boards.
200 families = [family.strip()
201 for family in task_info.board_families.split(',')]
202 for family in families:
203 self.boards += board_family_config.get(family, [])
Xixuan Wu89897182019-01-03 15:28:01 -0800204
Po-Hsien Wangdd833072018-08-16 18:09:20 -0700205 if task_info.exclude_board_families:
206 # Finetune the disallowed boards list with exclude_board_families
207 # & exclude_boards.
208 families = [family.strip()
209 for family in task_info.exclude_board_families.split(',')]
210 for family in families:
211 self.exclude_boards += board_family_config.get(family, [])
212
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700213 if tot is None:
214 self.tot_manager = tot_manager.TotMilestoneManager()
215 else:
216 self.tot_manager = tot
217
Xinan Linc8647112020-02-04 16:45:56 -0800218 self.dimensions = task_info.dimensions
219
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700220 self._set_spec_compare_info()
Xinan Lin33937d62020-04-14 14:41:23 -0700221 # Sanity test does not have to upload metrics.
222 if not is_sanity:
223 self.job_section = analytics.ScheduleJobSection(task_info)
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700224
Garry Wangdce77572021-07-18 19:33:35 -0700225 self.is_multi_dut_testing = False
226 if task_info.multi_dut_boards:
227 self.is_multi_dut_testing = True
228 self.multi_dut_boards = [
229 t.strip().split(';') for t in task_info.multi_dut_boards.split(',')]
230 else:
231 self.multi_dut_boards = []
232
233 if task_info.multi_dut_models:
234 self.is_multi_dut_testing = True
235 self.multi_dut_models = [
236 t.strip().split(';') for t in task_info.multi_dut_models.split(',')]
237 else:
238 self.multi_dut_models = []
239
Xinan Lin028f9582019-12-11 10:55:33 -0800240 def schedule(self, launch_control_builds, cros_builds_tuple,
241 firmware_builds, configs):
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700242 """Schedule the task by its settings.
243
244 Args:
245 launch_control_builds: the build dict for Android boards, see
246 return value of |get_launch_control_builds|.
Craig Bergstrom58263d32018-04-26 14:11:35 -0600247 cros_builds_tuple: the two-tuple of build dicts for ChromeOS boards,
248 see return value of |get_cros_builds|.
Xinan Lin028f9582019-12-11 10:55:33 -0800249 firmware_builds: a dict of firmware artifact, see return value of
250 |base_event.get_firmware_builds|.
Xixuan Wuf4a4c882019-03-15 14:48:26 -0700251 configs: a config_reader.Configs object.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700252
253 Raises:
254 SchedulingError: if tasks that should be scheduled fail to schedule.
Xixuan Wu5451a662017-10-17 10:57:40 -0700255
256 Returns:
Jacob Kopczynski79d00102018-07-13 15:37:03 -0700257 A boolean indicator; true if there were any suites related to this
258 task which got pushed into the suites queue.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700259 """
Xixuan Wuf4a4c882019-03-15 14:48:26 -0700260 assert configs.lab_config is not None
Xixuan Wu5451a662017-10-17 10:57:40 -0700261 self.is_pushed = False
262
Craig Bergstrom58263d32018-04-26 14:11:35 -0600263 branch_builds, relaxed_builds = cros_builds_tuple
264 builds_dict = branch_builds
265 if self.only_hwtest_sanity_required:
Xinan Linae7d6372019-09-12 14:42:10 -0700266 builds_dict = _split_unibuilds(relaxed_builds, configs)
Craig Bergstrom58263d32018-04-26 14:11:35 -0600267
Xinan Lindf0698a2020-02-05 22:38:11 -0800268 # Record all target boards and models into job section.
269 lab_config = configs.lab_config
270 models_by_board = lab_config.get_cros_model_map() if lab_config else {}
271 boards = self.boards if self.boards else lab_config.get_cros_board_list()
272 for b in boards:
273 if self.exclude_boards and b in self.exclude_boards:
274 continue
275 self.job_section.add_board(b)
276 models = self.models or models_by_board.get(b, [])
277 for m in models:
278 if m and '%s_%s' % (b, m) not in self.exclude_models:
279 self.job_section.add_model(m)
280
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700281 logging.info('######## Scheduling task %s ########', self.name)
282 if self.os_type == build_lib.OS_TYPE_CROS:
Craig Bergstrom58263d32018-04-26 14:11:35 -0600283 if not builds_dict:
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700284 logging.info('No CrOS build to run, skip running.')
285 else:
Xinan Lin028f9582019-12-11 10:55:33 -0800286 self._schedule_cros_builds(builds_dict, firmware_builds, configs)
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700287 else:
288 if not launch_control_builds:
289 logging.info('No Android build to run, skip running.')
290 else:
291 self._schedule_launch_control_builds(launch_control_builds)
Xinan Lindf0698a2020-02-05 22:38:11 -0800292 upload_result = False
293 try:
294 upload_result = self.job_section.upload()
295 # For any exceptions from BQ, only log it and move on.
296 except Exception as e: #pylint: disable=broad-except
297 logging.exception(str(e))
298 if not upload_result:
299 logging.warning('Failed to insert row: %r', self.job_section)
Xixuan Wu5451a662017-10-17 10:57:40 -0700300 return self.is_pushed
301
Garry Wangdce77572021-07-18 19:33:35 -0700302 def schedule_multi_duts(self, cros_builds_tuple,
Garry Wang4e29b512021-08-26 19:32:59 -0700303 recent_cros_builds_tuple, configs):
Garry Wangdce77572021-07-18 19:33:35 -0700304 """Schedule the multi-DUTs task by its settings.
305
306 Args:
307 cros_builds_tuple: the two-tuple of build dicts for ChromeOS boards,
308 see return value of |get_cros_builds|.
Garry Wang4e29b512021-08-26 19:32:59 -0700309 recent_cros_builds_tuple: Same as cros_builds_tuple, but contains
310 build info with a wider time range.
Garry Wangdce77572021-07-18 19:33:35 -0700311 configs: a config_reader.Configs object.
312
313 Raises:
314 SchedulingError: if tasks that should be scheduled fail to schedule.
315
316 Returns:
317 A boolean indicator; true if there were any suites related to this
318 task which got pushed into the suites queue.
319 """
320 assert configs.lab_config is not None
321 # For multi-DUTs We allow either request by board or request by model,
322 # but not both.
323 if self.multi_dut_boards and self.multi_dut_models:
324 logging.error('Both board and model presented in request but expecting'
325 'only one of them present at a time.')
326 return False
327 self.is_pushed = False
328
329 branch_builds, relaxed_builds = cros_builds_tuple
Garry Wang4e29b512021-08-26 19:32:59 -0700330 recent_branch_builds, recent_relaxed_builds = recent_cros_builds_tuple
Garry Wangdce77572021-07-18 19:33:35 -0700331 builds_dict = branch_builds
Garry Wang4e29b512021-08-26 19:32:59 -0700332 recent_builds_dict = recent_branch_builds
Garry Wangdce77572021-07-18 19:33:35 -0700333 if self.only_hwtest_sanity_required:
334 builds_dict = _split_unibuilds(relaxed_builds, configs)
335 # We don't need to expand build info to model level as
336 # secondary DUTs will have their builds determined by
337 # their board name.
Garry Wang4e29b512021-08-26 19:32:59 -0700338 recent_builds_dict = recent_relaxed_builds
Garry Wangdce77572021-07-18 19:33:35 -0700339
340 # Record multi-DUTs testing only for primary boards as we're
341 # going to have a replacement for SS and analytics layer soon.
342 build_targets_dict = self._get_multi_duts_build_targets_dict(configs)
343 model_set = set()
344 for board, groups in build_targets_dict.items():
345 self.job_section.add_board(board)
346 for group in groups:
347 if group[0].model:
348 model_set.add(group[0].model)
349 for model in model_set:
350 if self.exclude_models and model not in self.exclude_models:
351 self.job_section.add_model(model)
352
353 logging.info('######## Scheduling task %s ########', self.name)
354 if self.os_type != build_lib.OS_TYPE_CROS:
Garry Wang6ca42dd2022-02-28 21:54:19 -0800355 logging.info('Multi-DUTs testing only support CrOS builds.')
Garry Wangdce77572021-07-18 19:33:35 -0700356 return False
357 if not builds_dict:
358 logging.info('No CrOS build to run, skip running.')
359 return False
Garry Wang4e29b512021-08-26 19:32:59 -0700360 if not recent_builds_dict:
361 logging.info('No recent CrOS build for secondary DUTs to run,'
Garry Wangdce77572021-07-18 19:33:35 -0700362 ' skip running.')
363 return False
364 self._schedule_multi_duts_cros_builds(builds_dict,
Garry Wang4e29b512021-08-26 19:32:59 -0700365 recent_builds_dict, configs)
Garry Wangdce77572021-07-18 19:33:35 -0700366
367 upload_result = False
368 try:
369 upload_result = self.job_section.upload()
370 # For any exceptions from BQ, only log it and move on.
371 except Exception as e: #pylint: disable=broad-except
372 logging.exception(str(e))
373 if not upload_result:
374 logging.warning('Failed to insert row: %r', self.job_section)
375 return self.is_pushed
376
377 def _model_to_board_dict(self, models_by_board):
378 """Build a model to board dict based on model by boards dict."""
379 d = {}
380 for board, models in models_by_board.items():
381 for model in models:
382 d[model] = board
383 return d
384
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700385 def _set_spec_compare_info(self):
386 """Set branch spec compare info for task for further check."""
387 self._bare_branches = []
388 self._version_equal_constraint = False
389 self._version_gte_constraint = False
390 self._version_lte_constraint = False
391
392 if not self.branch_specs:
393 # Any milestone is OK.
394 self._numeric_constraint = version.LooseVersion('0')
395 else:
396 self._numeric_constraint = None
397 for spec in self.branch_specs:
398 if 'tot' in spec.lower():
399 # Convert spec >=tot-1 to >=RXX.
400 tot_str = spec[spec.index('tot'):]
401 spec = spec.replace(
402 tot_str, self.tot_manager.convert_tot_spec(tot_str))
403
404 if spec.startswith('>='):
405 self._numeric_constraint = version.LooseVersion(
406 spec.lstrip('>=R'))
407 self._version_gte_constraint = True
408 elif spec.startswith('<='):
409 self._numeric_constraint = version.LooseVersion(
410 spec.lstrip('<=R'))
411 self._version_lte_constraint = True
412 elif spec.startswith('=='):
413 self._version_equal_constraint = True
414 self._numeric_constraint = version.LooseVersion(
415 spec.lstrip('==R'))
416 else:
417 self._bare_branches.append(spec)
418
419 def _fits_spec(self, branch):
420 """Check if a branch is deemed OK by this task's branch specs.
421
422 Will return whether a branch 'fits' the specifications stored in this task.
423
424 Examples:
425 Assuming tot=R40
426 t = Task('Name', 'suite', ['factory', '>=tot-1'])
427 t._fits_spec('factory') # True
428 t._fits_spec('40') # True
429 t._fits_spec('38') # False
430 t._fits_spec('firmware') # False
431
432 Args:
433 branch: the branch to check.
434
435 Returns:
436 True if branch 'fits' with stored specs, False otherwise.
437 """
438 if branch in build_lib.BARE_BRANCHES:
439 return branch in self._bare_branches
440
441 if self._numeric_constraint:
442 if self._version_equal_constraint:
443 return version.LooseVersion(branch) == self._numeric_constraint
444 elif self._version_gte_constraint:
445 return version.LooseVersion(branch) >= self._numeric_constraint
446 elif self._version_lte_constraint:
447 return version.LooseVersion(branch) <= self._numeric_constraint
448 else:
449 return version.LooseVersion(branch) >= self._numeric_constraint
450 else:
451 return False
452
Xinan Lin028f9582019-12-11 10:55:33 -0800453 def _get_firmware_build(self, spec, board, firmware_build_dict, lab_config):
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700454 """Get the firmware build name to test with ChromeOS build.
455
456 Args:
457 spec: a string build spec for RO or RW firmware, eg. firmware,
458 cros. For RO firmware, the value can also be released_ro_X.
459 board: a string board against which this task will run suite job.
Xinan Lin028f9582019-12-11 10:55:33 -0800460 firmware_build_dict: a dict of firmware artifacts, see return value of
461 |base_event.get_firmware_builds|.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700462 lab_config: a config.LabConfig object, to read lab config file.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700463
464 Returns:
465 A string firmware build name.
466
467 Raises:
468 ValueError: if failing to get firmware from lab config file;
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700469 """
470 if not spec or spec == 'stable':
471 # TODO(crbug.com/577316): Query stable RO firmware.
472 logging.debug('%s RO firmware build is not supported.', spec)
473 return None
474
475 try:
Dhanya Ganeshf6014b72020-08-04 22:33:28 +0000476 if firmware_build_dict:
Xinan Lin028f9582019-12-11 10:55:33 -0800477 return firmware_build_dict.get((spec, board), None)
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700478 else:
Xinan Lin028f9582019-12-11 10:55:33 -0800479 return None
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700480 except ValueError as e:
481 logging.warning('Failed to get firmware from lab config file'
482 'for spec %s, board %s: %s', spec, board, str(e))
483 return None
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700484
C Shapiro7f24a002017-12-05 14:25:09 -0700485 def _push_suite(
486 self,
Xinan Lindf0698a2020-02-05 22:38:11 -0800487 task_id=None,
C Shapiro7f24a002017-12-05 14:25:09 -0700488 board=None,
489 model=None,
490 cros_build=None,
491 firmware_rw_build=None,
492 firmware_ro_build=None,
493 test_source_build=None,
494 launch_control_build=None,
Garry Wangdce77572021-07-18 19:33:35 -0700495 run_prod_code=False,
496 secondary_targets=None):
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700497 """Schedule suite job for the task by pushing suites to SuiteQueue.
498
499 Args:
Xinan Lindf0698a2020-02-05 22:38:11 -0800500 task_id: the id to track this task in exectuion.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700501 board: the board against which this suite job run.
C Shapiro7f24a002017-12-05 14:25:09 -0700502 model: the model name for unibuild.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700503 cros_build: the CrOS build of this suite job.
504 firmware_rw_build: Firmware RW build to run this suite job with.
505 firmware_ro_build: Firmware RO build to run this suite job with.
506 test_source_build: Test source build, used for server-side
507 packaging of this suite job.
508 launch_control_build: the launch control build of this suite job.
509 run_prod_code: If True, the suite will run the test code that lives
510 in prod aka the test code currently on the lab servers. If
511 False, the control files and test code for this suite run will
512 be retrieved from the build artifacts. Default is False.
Garry Wangdce77572021-07-18 19:33:35 -0700513 secondary_targets: A list of BuildTarget namedtuple to represent
514 required secondary DUTs info in this suite job run.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700515 """
516 android_build = None
517 testbed_build = None
518
519 if self.testbed_dut_count:
520 launch_control_build = '%s#%d' % (launch_control_build,
521 self.testbed_dut_count)
522 test_source_build = launch_control_build
523 board = '%s-%d' % (board, self.testbed_dut_count)
524
525 if launch_control_build:
526 if not self.testbed_dut_count:
527 android_build = launch_control_build
528 else:
529 testbed_build = launch_control_build
530
Garry Wang111a26f2021-07-23 15:25:14 -0700531 if secondary_targets:
532 secondary_targets = convert_secondary_targets_to_string(secondary_targets)
533 else:
534 secondary_targets = ''
535
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700536 suite_job_parameters = {
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700537 build_lib.BuildVersionKey.ANDROID_BUILD_VERSION: android_build,
Jacob Kopczynskic1d0f5c2020-08-09 10:41:32 -0700538 build_lib.BuildVersionKey.CROS_VERSION: cros_build,
539 build_lib.BuildVersionKey.FW_RO_VERSION: firmware_ro_build,
540 build_lib.BuildVersionKey.FW_RW_VERSION: firmware_rw_build,
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700541 build_lib.BuildVersionKey.TESTBED_BUILD_VERSION: testbed_build,
Taylor Clark8b06a832021-02-16 21:45:42 +0000542 'analytics_name': self.analytics_name,
Jacob Kopczynskic1d0f5c2020-08-09 10:41:32 -0700543 'board': board,
544 'dimensions': self.dimensions,
545 'force': self.force,
546 'job_retry': self.job_retry,
547 'max_runtime_mins': _JOB_MAX_RUNTIME_MINS_DEFAULT,
548 'model': model,
549 'name': self.name,
550 'no_delay': self.no_delay,
551 'no_wait_for_results': not self.job_retry,
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700552 'num': self.num,
553 'pool': self.pool,
554 'priority': self.priority,
Jacob Kopczynskic1d0f5c2020-08-09 10:41:32 -0700555 'qs_account': self.qs_account,
556 'run_prod_code': run_prod_code,
Garry Wangdce77572021-07-18 19:33:35 -0700557 'secondary_targets': secondary_targets,
Jacob Kopczynskic1d0f5c2020-08-09 10:41:32 -0700558 'suite': self.suite,
559 'task_id': task_id,
560 'test_source_build': test_source_build,
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700561 'timeout': self.timeout,
562 'timeout_mins': _JOB_MAX_RUNTIME_MINS_DEFAULT,
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700563 }
564
Xinan Lin9e4917d2019-11-04 10:58:47 -0800565 task_executor.push(task_executor.SUITES_QUEUE,
566 tag=self.suite,
567 **suite_job_parameters)
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700568 logging.info('Pushing task %r into taskqueue', suite_job_parameters)
Xixuan Wu5451a662017-10-17 10:57:40 -0700569 self.is_pushed = True
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700570
Xinan Lin028f9582019-12-11 10:55:33 -0800571 def _schedule_cros_builds(self, build_dict, firmware_build_dict, configs):
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700572 """Schedule tasks with branch builds.
573
574 Args:
Craig Bergstrom58263d32018-04-26 14:11:35 -0600575 build_dict: the build dict for ChromeOS boards, see return
Xinan Linea1efcb2019-12-30 23:46:42 -0800576 value of |build_lib.get_cros_builds|.
Xinan Lin028f9582019-12-11 10:55:33 -0800577 firmware_build_dict: a dict of firmware artifact, see return value of
578 |base_event.get_firmware_builds|.
Xixuan Wuf4a4c882019-03-15 14:48:26 -0700579 configs: A config_reader.Configs object.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700580 """
Xixuan Wuf4a4c882019-03-15 14:48:26 -0700581 lab_config = configs.lab_config
C Shapiro7f24a002017-12-05 14:25:09 -0700582 models_by_board = lab_config.get_cros_model_map() if lab_config else {}
C Shapiro09108252019-08-01 14:52:52 -0500583 model_agnostic_cros_builds = set()
Xixuan Wu8d2f2862018-08-28 16:48:04 -0700584 for (board, passed_model, build_type,
Craig Bergstrom58263d32018-04-26 14:11:35 -0600585 milestone), manifest in build_dict.iteritems():
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700586 cros_build = str(build_lib.CrOSBuild(board, build_type, milestone,
587 manifest))
588 logging.info('Running %s on %s', self.name, cros_build)
Po-Hsien Wang6d589732018-05-15 17:19:34 -0700589 if self.exclude_boards and board in self.exclude_boards:
590 logging.debug('Board %s is in excluded board list: %s',
591 board, self.exclude_boards)
592 continue
Xixuan Wu8d2f2862018-08-28 16:48:04 -0700593
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700594 if self.boards and board not in self.boards:
595 logging.debug('Board %s is not in supported board list: %s',
596 board, self.boards)
597 continue
598
599 # Check the fitness of the build's branch for task
600 branch_build_spec = _pick_branch(build_type, milestone)
601 if not self._fits_spec(branch_build_spec):
Xinan Lindf0698a2020-02-05 22:38:11 -0800602 msg = ("branch_build spec %s doesn't fit this task's "
603 "requirement: %s") % (branch_build_spec,
604 ",".join(self.branch_specs))
605 logging.debug(msg)
606 self.job_section.add_schedule_job(board, passed_model, msg=msg)
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700607 continue
608
Xinan Lindf0698a2020-02-05 22:38:11 -0800609 # Record this build as it matches both board and branch specs.
610 if self.only_hwtest_sanity_required:
611 self.job_section.add_matched_relax_build(
612 board, build_type, milestone, manifest)
613 else:
614 self.job_section.add_matched_build(
615 board, build_type, milestone, manifest)
616
Sean McAllister7378b692021-06-03 15:44:22 -0600617 # check that we only got one spec for ro firmware
618 if all([self.firmware_ro_version, self.firmware_ro_build_spec]):
619 logging.error(
620 "Exactly one of firmware_ro_version or firmware_ro_build_spec " \
621 "allowed, got: %s and %s" % (
622 self.firmware_ro_version,
623 self.firmware_ro_build_spec,
624 ),
625 )
626 continue
627
628 # check that we only got one spec for rw firmware
629 if all([self.firmware_rw_version, self.firmware_rw_build_spec]):
630 logging.error(
631 "Exactly one of firmware_rw_version or firmware_rw_build_spec " \
632 "allowed, got: %s and %s" % (
633 self.firmware_rw_version,
634 self.firmware_rw_build_spec,
635 ),
636 )
637 continue
638
639 # resolve ro firmware version to provision
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700640 firmware_ro_build = None
Brigit Rossbachbb080912020-11-18 13:52:17 -0700641 if self.firmware_ro_version:
642 firmware_ro_build = self.firmware_ro_version
Sean McAllister7378b692021-06-03 15:44:22 -0600643 elif self.firmware_ro_build_spec:
644 firmware_ro_build = self._get_firmware_build(
645 self.firmware_ro_build_spec, board, firmware_build_dict, lab_config)
Brigit Rossbachbb080912020-11-18 13:52:17 -0700646
Sean McAllister7378b692021-06-03 15:44:22 -0600647 if not firmware_ro_build:
648 msg = 'No RO firmware build to run, skip running'
649 logging.debug(msg)
650 self.job_section.add_schedule_job(board, passed_model, msg=msg)
651 continue
652
653 # resolve rw firmware version to provision
654 firmware_rw_build = None
Brigit Rossbachbb080912020-11-18 13:52:17 -0700655 if self.firmware_rw_version:
656 firmware_rw_build = self.firmware_rw_version
Sean McAllister7378b692021-06-03 15:44:22 -0600657 elif self.firmware_rw_build_spec:
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700658 firmware_rw_build = self._get_firmware_build(
Sean McAllister7378b692021-06-03 15:44:22 -0600659 self.firmware_rw_build_spec, board, firmware_build_dict, lab_config)
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700660
Sean McAllister7378b692021-06-03 15:44:22 -0600661 if not firmware_rw_build:
662 msg = 'No RW firmware build to run, skip running'
663 logging.debug(msg)
664 self.job_section.add_schedule_job(board, passed_model, msg=msg)
665 continue
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700666
667 if self.test_source == build_lib.BuildType.FIRMWARE_RW:
668 test_source_build = firmware_rw_build
669 else:
Jacob Kopczynski79d00102018-07-13 15:37:03 -0700670 # Default test source build to CrOS build if it's not specified.
671 # Past versions chose based on run_prod_code, but we no longer respect
672 # that option and scheduler settings should always set it to False.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700673 test_source_build = cros_build
674
Xinan Lindf0698a2020-02-05 22:38:11 -0800675 # Record the matched firwmare build.
676 if firmware_rw_build:
677 self.job_section.add_matched_fw_build(
678 board,
679 self.firmware_rw_build_spec,
680 firmware_rw_build,
681 read_only=False)
682 if firmware_ro_build:
683 self.job_section.add_matched_fw_build(
684 board,
685 self.firmware_ro_build_spec,
686 firmware_ro_build,
687 read_only=True)
688
Xinan Lin6668e0f2020-05-29 10:02:57 -0700689 # Board above is used as build target to control the CrOS image.
690 # The following part is to assign models for lab boards, where
691 # the suffix should be removed.
Prathmesh Prabhu074b8d42020-08-13 17:17:33 +0000692 hwtest_board = build_lib.reshape_board(board)
693
Xinan Lin6668e0f2020-05-29 10:02:57 -0700694 models = models_by_board.get(hwtest_board, [None])
C Shapiro7f24a002017-12-05 14:25:09 -0700695
Harpreet Grewalbbbb7de2019-02-05 19:35:03 +0000696 for model in models:
697 if ((passed_model is not None and model == passed_model) or
698 passed_model is None):
Xinan Lin6668e0f2020-05-29 10:02:57 -0700699 full_model_name = '%s_%s' % (hwtest_board, model)
Xixuan Wua41efa22019-05-17 14:28:04 -0700700 # Respect exclude first.
701 if self.exclude_models and full_model_name in self.exclude_models:
702 logging.debug("Skip model %s as it's in exclude model list %s",
703 model, self.exclude_models)
704 continue
705
706 if self.models and full_model_name not in self.models:
707 logging.debug("Skip model %s as it's not in support model list %s",
708 model, self.models)
709 continue
710
C Shapiro09108252019-08-01 14:52:52 -0500711 explicit_model = model
712
713 if self.any_model:
Xinan Lin3ba18a02019-08-13 15:44:55 -0700714 explicit_model = None
C Shapiro09108252019-08-01 14:52:52 -0500715 unique_build = str(cros_build)
716 if unique_build in model_agnostic_cros_builds:
717 # Skip since we've already run with no explicit model set.
Xinan Lindf0698a2020-02-05 22:38:11 -0800718 msg = "Skip model %s as any_model enabled for this job." % model
719 self.job_section.add_schedule_job(board, model, msg=msg)
C Shapiro09108252019-08-01 14:52:52 -0500720 continue
721 model_agnostic_cros_builds.add(unique_build)
722
Xinan Lindf0698a2020-02-05 22:38:11 -0800723 task_id = str(uuid.uuid1())
Harpreet Grewalbbbb7de2019-02-05 19:35:03 +0000724 self._push_suite(
Xinan Lindf0698a2020-02-05 22:38:11 -0800725 task_id=task_id,
Xixuan Wub4b2f412019-05-03 11:22:31 -0700726 board=hwtest_board,
C Shapiro09108252019-08-01 14:52:52 -0500727 model=explicit_model,
Harpreet Grewalbbbb7de2019-02-05 19:35:03 +0000728 cros_build=cros_build,
729 firmware_rw_build=firmware_rw_build,
730 firmware_ro_build=firmware_ro_build,
Xinan Lin0550f492020-01-21 16:25:53 -0800731 test_source_build=test_source_build)
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700732
Xinan Lin6668e0f2020-05-29 10:02:57 -0700733 # Analytics table stores the build target instead of the lab board.
Xinan Lin0673a182020-04-14 15:09:35 -0700734 self.job_section.add_schedule_job(
735 board, explicit_model, task_id=task_id)
Xinan Lindf0698a2020-02-05 22:38:11 -0800736
Garry Wangdce77572021-07-18 19:33:35 -0700737 def _schedule_multi_duts_cros_builds(self, build_dict,
Garry Wang4e29b512021-08-26 19:32:59 -0700738 recent_build_dict, configs):
Garry Wangdce77572021-07-18 19:33:35 -0700739 """Schedule multi-DUTs tasks with branch builds.
740
741 Args:
742 build_dict: the build dict for ChromeOS boards, see return
743 value of |build_lib.get_cros_builds|.
Garry Wang4e29b512021-08-26 19:32:59 -0700744 recent_build_dict: Same as build_dict, but contains build info
745 with a wider time range.
Garry Wangdce77572021-07-18 19:33:35 -0700746 configs: A config_reader.Configs object.
747 """
748 build_targets_dict = self._get_multi_duts_build_targets_dict(configs)
Garry Wang6ca42dd2022-02-28 21:54:19 -0800749 android_boards_list = configs.lab_config.get_android_model_map().keys()
Garry Wangdce77572021-07-18 19:33:35 -0700750 model_agnostic_builds = set()
751 for (board, passed_model, build_type,
752 milestone), manifest in build_dict.iteritems():
753 cros_build = str(build_lib.CrOSBuild(board, build_type, milestone,
754 manifest))
755 logging.info('Running %s on %s', self.name, cros_build)
756 if board not in build_targets_dict.keys():
757 logging.debug('Board %s is not in primary board list: %s',
758 board, build_targets_dict.keys())
759 continue
760
761 # Check the fitness of the build's branch for task
762 branch_build_spec = _pick_branch(build_type, milestone)
763 if not self._fits_spec(branch_build_spec):
764 msg = ("branch_build spec %s doesn't fit this task's "
765 "requirement: %s") % (branch_build_spec,
766 ",".join(self.branch_specs))
767 logging.debug(msg)
768 self.job_section.add_schedule_job(board, passed_model, msg=msg)
769 continue
770
771 # Record this build as it matches both board and branch specs.
772 if self.only_hwtest_sanity_required:
773 self.job_section.add_matched_relax_build(
774 board, build_type, milestone, manifest)
775 else:
776 self.job_section.add_matched_build(
777 board, build_type, milestone, manifest)
778
779 # Board above is used as build target to control the CrOS image.
780 # The following part is to assign models for lab boards, where
781 # the suffix should be removed.
782 hwtest_board = build_lib.reshape_board(board)
783 for group in build_targets_dict.get(board):
784 joined_board_strings = '_'.join(dut.board for dut in group)
785 build_tag = '%s_%s' % (joined_board_strings, cros_build)
786 primary_dut = group[0]
787 if primary_dut.model:
788 if primary_dut.model != passed_model:
789 # Explict model specified but not match, so skip to next.
Garry Wang450b6432022-04-21 14:25:32 -0700790 logging.info(
791 "Explict model %s specified but doesn't match with passed"
792 " model %s, skipping.", primary_dut.model, passed_model)
Garry Wangdce77572021-07-18 19:33:35 -0700793 continue
794 elif build_tag in model_agnostic_builds:
795 msg = ("Skip model %s as any_model enabled for this job."
796 % passed_model)
797 self.job_section.add_schedule_job(board, passed_model, msg=msg)
798 continue
799
Garry Wang4e29b512021-08-26 19:32:59 -0700800 # Determine cros builds for secondary DUTs from recent build dict.
Garry Wangdce77572021-07-18 19:33:35 -0700801 secondary_targets = []
802 for s_dut in group[1:]:
Garry Wang6ca42dd2022-02-28 21:54:19 -0800803 if s_dut.board in android_boards_list:
804 # We don't need to provision Android devices for CrOS test.
805 secondary_targets.append(
806 BuildTarget(s_dut.board, s_dut.model, None))
807 continue
Garry Wangdce77572021-07-18 19:33:35 -0700808 s_key = (s_dut.board, None, build_type, milestone)
Garry Wang4e29b512021-08-26 19:32:59 -0700809 s_manifest = recent_build_dict.get(s_key, '')
Garry Wangdce77572021-07-18 19:33:35 -0700810 if s_manifest:
811 s_cros_build = str(build_lib.CrOSBuild(
812 s_dut.board, build_type, milestone, s_manifest))
813 s_hwtest_board = build_lib.reshape_board(s_dut.board)
814 secondary_targets.append(
815 BuildTarget(s_hwtest_board, s_dut.model, s_cros_build))
816 # Check if we get cros build for all secondary DUTs.
817 if len(secondary_targets) != len(group[1:]):
818 logging.info('Cannot determine cros version for all secondary'
819 ' DUTs, skip.')
820 continue
821
822 # Schedule task.
823 model_agnostic_builds.add(build_tag)
824 task_id = str(uuid.uuid1())
825 self._push_suite(
826 task_id = task_id,
827 board=hwtest_board,
828 model=primary_dut.model,
829 cros_build=cros_build,
830 test_source_build=cros_build,
831 secondary_targets=secondary_targets
832 )
833
834 # Analytics table stores the build target instead of the lab board.
835 self.job_section.add_schedule_job(
836 board, primary_dut.model, task_id=task_id)
837
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700838 def _schedule_launch_control_builds(self, launch_control_builds):
839 """Schedule tasks with launch control builds.
840
841 Args:
842 launch_control_builds: the build dict for Android boards.
843 """
844 for board, launch_control_build in launch_control_builds.iteritems():
845 logging.debug('Running %s on %s', self.name, board)
Po-Hsien Wang6d589732018-05-15 17:19:34 -0700846 if self.exclude_boards and board in self.exclude_boards:
847 logging.debug('Board %s is in excluded board list: %s',
848 board, self.exclude_boards)
849 continue
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700850 if self.boards and board not in self.boards:
851 logging.debug('Board %s is not in supported board list: %s',
852 board, self.boards)
853 continue
854
855 for android_build in launch_control_build:
856 if not any([branch in android_build
857 for branch in self.launch_control_branches]):
858 logging.debug('Branch %s is not required to run for task '
859 '%s', android_build, self.name)
860 continue
861
862 self._push_suite(board=board,
863 test_source_build=android_build,
864 launch_control_build=android_build)
865
Garry Wangdce77572021-07-18 19:33:35 -0700866 def _get_multi_duts_build_targets_dict(self, configs):
867 """Generate a dict contains all build targets info based on
868 self.multi_dut_boards or self.multi_dut_models.
869
870 Args:
871 configs: A config_reader.Configs object.
872
873 Returns:
874 A dict where key is a cros board, and value is a list that represents
875 group of DUTs where primary DUT's board equal to key. Each group of
876 DUTs are represented by a list of BuildTarget. Example:
877 {
878 'coral': [
879 [
880 BuildTarget(board='coral', model='babytiger', cros_build=None),
881 BuildTarget(board='eve', model=None, cros_build=None),
882 BuildTarget(board='nami', model='None', cros_build=None)
883 ],
884 [
885 BuildTarget(board='coral', model='blacktiplte', cros_build=None),
886 BuildTarget(board='octopus', model=None, cros_build=None),
887 BuildTarget(board='nami', model=None, cros_build=None)],
888 ]
889 ]
890 }
891 """
892 lab_config = configs.lab_config
893 build_targets = defaultdict(lambda: [])
Garry Wang6ca42dd2022-02-28 21:54:19 -0800894 cros_models_by_board = lab_config.get_cros_model_map() if lab_config else {}
895 android_models_by_board = lab_config.get_android_model_map() if lab_config else {}
896 model_to_board_dict = self._model_to_board_dict(cros_models_by_board)
897 # Multi-DUTs support Android as secondary devices, so we need to update
898 # the dict to include Android boards/models.
899 model_to_board_dict.update(self._model_to_board_dict(android_models_by_board))
900
Garry Wangdce77572021-07-18 19:33:35 -0700901 # Handle the case when multi-DUTs testing requested by board.
902 for group in self.multi_dut_boards:
903 primary_board = group[0]
904 if self.any_model:
905 new_group = [BuildTarget(board, None, None) for board in group]
906 build_targets[primary_board].append(new_group)
907 else:
908 # If any_model disbled, we need to have each model from primary_board
909 # family pair with requested secondary devices.
910 # Board above is used as build target to control the CrOS image.
911 # The following part is to assign models for lab boards, where
912 # the suffix should be removed.
913 hwtest_board = build_lib.reshape_board(primary_board)
Garry Wang6ca42dd2022-02-28 21:54:19 -0800914 models = cros_models_by_board.get(hwtest_board, [None])
Garry Wangdce77572021-07-18 19:33:35 -0700915 for model in models:
916 if self.exclude_models and model in self.exclude_models:
917 logging.info('Model %s is in exclude list, skipping.' % model)
918 continue
919 new_group = []
920 new_group.append(BuildTarget(primary_board, model, None))
921 for board in group[1:]:
922 new_group.append(BuildTarget(board, None, None))
923 build_targets[primary_board].append(new_group)
924 # Handle the case when multi-DUTs testing requested by model.
925 for group in self.multi_dut_models:
926 boards = [model_to_board_dict.get(model, '') for model in group]
927 if '' in boards:
928 logging.info('Cannot find board name from one of requested model,'
929 ' skipping.')
930 continue
931 new_group = [BuildTarget(boards[i], group[i], None) for i in range(len(group))]
932 build_targets[boards[0]].append(new_group)
933 return build_targets
934
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700935
936def _pick_branch(build_type, milestone):
937 """Select branch based on build type.
938
939 If the build_type is a bare branch, return build_type as the build spec.
940 If the build_type is a normal CrOS branch, return milestone as the build
941 spec.
942
943 Args:
944 build_type: a string builder name, like 'release'.
945 milestone: a string milestone, like '55'.
946
947 Returns:
948 A string milestone if build_type represents CrOS build, otherwise
949 return build_type.
950 """
951 return build_type if build_type in build_lib.BARE_BRANCHES else milestone
Xinan Linae7d6372019-09-12 14:42:10 -0700952
953
954def _split_unibuilds(build_dict, configs):
955 """Split the uni-builds to all models under a board.
956
957 Args:
958 build_dict: the build dict for ChromeOS boards, see return
Xinan Linea1efcb2019-12-30 23:46:42 -0800959 value of |build_lib.get_cros_builds|.
Xinan Linae7d6372019-09-12 14:42:10 -0700960 configs: a config_reader.Configs object.
961
962 Returns:
963 A build dict.
964 """
965 models_by_board = configs.lab_config.get_cros_model_map()
966 if not models_by_board:
967 return build_dict
968 all_branch_build_dict = {}
969 for (board, model, config, milestone), platform in build_dict.iteritems():
970 uni_build_models = models_by_board.get(board)
971 if uni_build_models is not None and model is None:
972 for uni_build_model in uni_build_models:
973 model_key = (board, uni_build_model, config, milestone)
974 _add_build_dict(all_branch_build_dict, model_key, platform)
975 continue
976 build_key = (board, model, config, milestone)
977 _add_build_dict(all_branch_build_dict, build_key, platform)
978
979 return all_branch_build_dict
980
981
982def _add_build_dict(build_dict, key, value):
983 """A wrapper to add or update an item in build_dict."""
984 cur_manifest = build_dict.get(key)
985 if cur_manifest is None:
986 build_dict[key] = value
987 return
988 build_dict[key] = max(
989 [cur_manifest, value], key=version.LooseVersion)