blob: 90ff0345563cc3053bd53e44b2aec6aa4658f00a [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.
Azizur Rahmanb720e512022-05-13 18:30:40 +0000126 run_via_cft, a boolean to specify if current task should be scheduled
127 to run via CFT test execution path.
Jacob Kopczynskic54520f2020-08-07 13:20:12 -0700128
Xixuan Wu83118dd2018-08-27 12:11:35 -0700129 board_family_config: A board family dictionary mapping board_family name
130 to its corresponding boards.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700131 tot: The tot manager for checking ToT. If it's None, a new tot_manager
132 instance will be initialized.
Xinan Lin33937d62020-04-14 14:41:23 -0700133 is_sanity: A boolean; true if we are running in sanity env.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700134 """
Xixuan Wu5451a662017-10-17 10:57:40 -0700135 # Indicate whether there're suites get pushed into taskqueue for this task.
136 self.is_pushed = False
137
Taylor Clark8b06a832021-02-16 21:45:42 +0000138 self.analytics_name = task_info.analytics_name
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700139 self.branch_specs = task_info.branch_specs
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700140 self.cros_build_spec = task_info.cros_build_spec
Xixuan Wu008ee832017-10-12 16:59:34 -0700141 self.day = task_info.day
Jacob Kopczynskic1d0f5c2020-08-09 10:41:32 -0700142 self.firmware_ro_build_spec = task_info.firmware_ro_build_spec
143 self.firmware_rw_build_spec = task_info.firmware_rw_build_spec
Brigit Rossbachbb080912020-11-18 13:52:17 -0700144 self.firmware_ro_version = task_info.firmware_ro_version
145 self.firmware_rw_version = task_info.firmware_rw_version
Jacob Kopczynskic1d0f5c2020-08-09 10:41:32 -0700146 self.force = task_info.force
147 self.frontdoor = task_info.frontdoor
148 self.hour = task_info.hour
149 self.job_retry = task_info.job_retry
150 self.name = task_info.name
151 self.no_delay = task_info.no_delay
152 self.num = task_info.num
Jared Loucks7068d8b2022-04-28 09:41:33 -0600153 self.only_successful_build_required = task_info.only_successful_build_required
Jacob Kopczynskic1d0f5c2020-08-09 10:41:32 -0700154 self.os_type = task_info.os_type
155 self.pool = task_info.pool
156 self.priority = task_info.priority
Xinan Lin4757d6f2020-03-24 22:20:31 -0700157 self.qs_account = task_info.qs_account
Jacob Kopczynskic1d0f5c2020-08-09 10:41:32 -0700158 self.suite = task_info.suite
159 self.test_source = task_info.test_source
160 self.testbed_dut_count = task_info.testbed_dut_count
161 self.timeout = task_info.timeout
Garry Wangdce77572021-07-18 19:33:35 -0700162 self.multi_dut_trigger = task_info.multi_dut_trigger
Azizur Rahmanb720e512022-05-13 18:30:40 +0000163 self.run_via_cft = task_info.run_via_cft
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700164
165 if task_info.lc_branches:
166 self.launch_control_branches = [
167 t.strip() for t in task_info.lc_branches.split(',')]
168 else:
169 self.launch_control_branches = []
170
171 if task_info.lc_targets:
172 self.launch_control_targets = [
173 t.strip() for t in task_info.lc_targets.split(',')]
174 else:
175 self.launch_control_targets = []
176
177 if task_info.boards:
178 self.boards = [t.strip() for t in task_info.boards.split(',')]
179 else:
180 self.boards = []
181
Po-Hsien Wang6d589732018-05-15 17:19:34 -0700182 if task_info.exclude_boards:
183 self.exclude_boards = [
184 t.strip() for t in task_info.exclude_boards.split(',')]
185 else:
186 self.exclude_boards = []
187
Xixuan Wu89897182019-01-03 15:28:01 -0800188 if task_info.models:
189 self.models = [t.strip() for t in task_info.models.split(',')]
190 else:
191 self.models = []
192
193 if task_info.exclude_models:
194 self.exclude_models = [
195 t.strip() for t in task_info.exclude_models.split(',')]
196 else:
197 self.exclude_models = []
198
C Shapiro09108252019-08-01 14:52:52 -0500199 self.any_model = task_info.any_model
200
Po-Hsien Wangdd833072018-08-16 18:09:20 -0700201 if task_info.board_families:
202 # Finetune the allowed boards list with board_families & boards.
203 families = [family.strip()
204 for family in task_info.board_families.split(',')]
205 for family in families:
206 self.boards += board_family_config.get(family, [])
Xixuan Wu89897182019-01-03 15:28:01 -0800207
Po-Hsien Wangdd833072018-08-16 18:09:20 -0700208 if task_info.exclude_board_families:
209 # Finetune the disallowed boards list with exclude_board_families
210 # & exclude_boards.
211 families = [family.strip()
212 for family in task_info.exclude_board_families.split(',')]
213 for family in families:
214 self.exclude_boards += board_family_config.get(family, [])
215
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700216 if tot is None:
217 self.tot_manager = tot_manager.TotMilestoneManager()
218 else:
219 self.tot_manager = tot
220
Xinan Linc8647112020-02-04 16:45:56 -0800221 self.dimensions = task_info.dimensions
222
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700223 self._set_spec_compare_info()
Xinan Lin33937d62020-04-14 14:41:23 -0700224 # Sanity test does not have to upload metrics.
225 if not is_sanity:
226 self.job_section = analytics.ScheduleJobSection(task_info)
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700227
Garry Wangdce77572021-07-18 19:33:35 -0700228 self.is_multi_dut_testing = False
229 if task_info.multi_dut_boards:
230 self.is_multi_dut_testing = True
231 self.multi_dut_boards = [
232 t.strip().split(';') for t in task_info.multi_dut_boards.split(',')]
233 else:
234 self.multi_dut_boards = []
235
236 if task_info.multi_dut_models:
237 self.is_multi_dut_testing = True
238 self.multi_dut_models = [
239 t.strip().split(';') for t in task_info.multi_dut_models.split(',')]
240 else:
241 self.multi_dut_models = []
242
Xinan Lin028f9582019-12-11 10:55:33 -0800243 def schedule(self, launch_control_builds, cros_builds_tuple,
244 firmware_builds, configs):
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700245 """Schedule the task by its settings.
246
247 Args:
248 launch_control_builds: the build dict for Android boards, see
249 return value of |get_launch_control_builds|.
Craig Bergstrom58263d32018-04-26 14:11:35 -0600250 cros_builds_tuple: the two-tuple of build dicts for ChromeOS boards,
251 see return value of |get_cros_builds|.
Xinan Lin028f9582019-12-11 10:55:33 -0800252 firmware_builds: a dict of firmware artifact, see return value of
253 |base_event.get_firmware_builds|.
Xixuan Wuf4a4c882019-03-15 14:48:26 -0700254 configs: a config_reader.Configs object.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700255
256 Raises:
257 SchedulingError: if tasks that should be scheduled fail to schedule.
Xixuan Wu5451a662017-10-17 10:57:40 -0700258
259 Returns:
Jacob Kopczynski79d00102018-07-13 15:37:03 -0700260 A boolean indicator; true if there were any suites related to this
261 task which got pushed into the suites queue.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700262 """
Xixuan Wuf4a4c882019-03-15 14:48:26 -0700263 assert configs.lab_config is not None
Xixuan Wu5451a662017-10-17 10:57:40 -0700264 self.is_pushed = False
265
Craig Bergstrom58263d32018-04-26 14:11:35 -0600266 branch_builds, relaxed_builds = cros_builds_tuple
267 builds_dict = branch_builds
Jared Loucks7068d8b2022-04-28 09:41:33 -0600268 if self.only_successful_build_required:
Xinan Linae7d6372019-09-12 14:42:10 -0700269 builds_dict = _split_unibuilds(relaxed_builds, configs)
Craig Bergstrom58263d32018-04-26 14:11:35 -0600270
Xinan Lindf0698a2020-02-05 22:38:11 -0800271 # Record all target boards and models into job section.
272 lab_config = configs.lab_config
273 models_by_board = lab_config.get_cros_model_map() if lab_config else {}
274 boards = self.boards if self.boards else lab_config.get_cros_board_list()
275 for b in boards:
276 if self.exclude_boards and b in self.exclude_boards:
277 continue
278 self.job_section.add_board(b)
279 models = self.models or models_by_board.get(b, [])
280 for m in models:
281 if m and '%s_%s' % (b, m) not in self.exclude_models:
282 self.job_section.add_model(m)
283
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700284 logging.info('######## Scheduling task %s ########', self.name)
285 if self.os_type == build_lib.OS_TYPE_CROS:
Craig Bergstrom58263d32018-04-26 14:11:35 -0600286 if not builds_dict:
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700287 logging.info('No CrOS build to run, skip running.')
288 else:
Xinan Lin028f9582019-12-11 10:55:33 -0800289 self._schedule_cros_builds(builds_dict, firmware_builds, configs)
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700290 else:
291 if not launch_control_builds:
292 logging.info('No Android build to run, skip running.')
293 else:
294 self._schedule_launch_control_builds(launch_control_builds)
Xinan Lindf0698a2020-02-05 22:38:11 -0800295 upload_result = False
296 try:
297 upload_result = self.job_section.upload()
298 # For any exceptions from BQ, only log it and move on.
299 except Exception as e: #pylint: disable=broad-except
300 logging.exception(str(e))
301 if not upload_result:
302 logging.warning('Failed to insert row: %r', self.job_section)
Xixuan Wu5451a662017-10-17 10:57:40 -0700303 return self.is_pushed
304
Garry Wangdce77572021-07-18 19:33:35 -0700305 def schedule_multi_duts(self, cros_builds_tuple,
Garry Wang4e29b512021-08-26 19:32:59 -0700306 recent_cros_builds_tuple, configs):
Garry Wangdce77572021-07-18 19:33:35 -0700307 """Schedule the multi-DUTs task by its settings.
308
309 Args:
310 cros_builds_tuple: the two-tuple of build dicts for ChromeOS boards,
311 see return value of |get_cros_builds|.
Garry Wang4e29b512021-08-26 19:32:59 -0700312 recent_cros_builds_tuple: Same as cros_builds_tuple, but contains
313 build info with a wider time range.
Garry Wangdce77572021-07-18 19:33:35 -0700314 configs: a config_reader.Configs object.
315
316 Raises:
317 SchedulingError: if tasks that should be scheduled fail to schedule.
318
319 Returns:
320 A boolean indicator; true if there were any suites related to this
321 task which got pushed into the suites queue.
322 """
323 assert configs.lab_config is not None
324 # For multi-DUTs We allow either request by board or request by model,
325 # but not both.
326 if self.multi_dut_boards and self.multi_dut_models:
327 logging.error('Both board and model presented in request but expecting'
328 'only one of them present at a time.')
329 return False
330 self.is_pushed = False
331
332 branch_builds, relaxed_builds = cros_builds_tuple
Garry Wang4e29b512021-08-26 19:32:59 -0700333 recent_branch_builds, recent_relaxed_builds = recent_cros_builds_tuple
Garry Wangdce77572021-07-18 19:33:35 -0700334 builds_dict = branch_builds
Garry Wang4e29b512021-08-26 19:32:59 -0700335 recent_builds_dict = recent_branch_builds
Jared Loucks7068d8b2022-04-28 09:41:33 -0600336 if self.only_successful_build_required:
Garry Wangdce77572021-07-18 19:33:35 -0700337 builds_dict = _split_unibuilds(relaxed_builds, configs)
338 # We don't need to expand build info to model level as
339 # secondary DUTs will have their builds determined by
340 # their board name.
Garry Wang4e29b512021-08-26 19:32:59 -0700341 recent_builds_dict = recent_relaxed_builds
Garry Wangdce77572021-07-18 19:33:35 -0700342
343 # Record multi-DUTs testing only for primary boards as we're
344 # going to have a replacement for SS and analytics layer soon.
345 build_targets_dict = self._get_multi_duts_build_targets_dict(configs)
346 model_set = set()
347 for board, groups in build_targets_dict.items():
348 self.job_section.add_board(board)
349 for group in groups:
350 if group[0].model:
351 model_set.add(group[0].model)
352 for model in model_set:
353 if self.exclude_models and model not in self.exclude_models:
354 self.job_section.add_model(model)
355
356 logging.info('######## Scheduling task %s ########', self.name)
357 if self.os_type != build_lib.OS_TYPE_CROS:
Garry Wang6ca42dd2022-02-28 21:54:19 -0800358 logging.info('Multi-DUTs testing only support CrOS builds.')
Garry Wangdce77572021-07-18 19:33:35 -0700359 return False
360 if not builds_dict:
361 logging.info('No CrOS build to run, skip running.')
362 return False
Garry Wang4e29b512021-08-26 19:32:59 -0700363 if not recent_builds_dict:
364 logging.info('No recent CrOS build for secondary DUTs to run,'
Garry Wangdce77572021-07-18 19:33:35 -0700365 ' skip running.')
366 return False
367 self._schedule_multi_duts_cros_builds(builds_dict,
Garry Wang4e29b512021-08-26 19:32:59 -0700368 recent_builds_dict, configs)
Garry Wangdce77572021-07-18 19:33:35 -0700369
370 upload_result = False
371 try:
372 upload_result = self.job_section.upload()
373 # For any exceptions from BQ, only log it and move on.
374 except Exception as e: #pylint: disable=broad-except
375 logging.exception(str(e))
376 if not upload_result:
377 logging.warning('Failed to insert row: %r', self.job_section)
378 return self.is_pushed
379
380 def _model_to_board_dict(self, models_by_board):
381 """Build a model to board dict based on model by boards dict."""
382 d = {}
383 for board, models in models_by_board.items():
384 for model in models:
385 d[model] = board
386 return d
387
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700388 def _set_spec_compare_info(self):
389 """Set branch spec compare info for task for further check."""
390 self._bare_branches = []
391 self._version_equal_constraint = False
392 self._version_gte_constraint = False
393 self._version_lte_constraint = False
394
395 if not self.branch_specs:
396 # Any milestone is OK.
397 self._numeric_constraint = version.LooseVersion('0')
398 else:
399 self._numeric_constraint = None
400 for spec in self.branch_specs:
401 if 'tot' in spec.lower():
402 # Convert spec >=tot-1 to >=RXX.
403 tot_str = spec[spec.index('tot'):]
404 spec = spec.replace(
405 tot_str, self.tot_manager.convert_tot_spec(tot_str))
406
407 if spec.startswith('>='):
408 self._numeric_constraint = version.LooseVersion(
409 spec.lstrip('>=R'))
410 self._version_gte_constraint = True
411 elif spec.startswith('<='):
412 self._numeric_constraint = version.LooseVersion(
413 spec.lstrip('<=R'))
414 self._version_lte_constraint = True
415 elif spec.startswith('=='):
416 self._version_equal_constraint = True
417 self._numeric_constraint = version.LooseVersion(
418 spec.lstrip('==R'))
419 else:
420 self._bare_branches.append(spec)
421
422 def _fits_spec(self, branch):
423 """Check if a branch is deemed OK by this task's branch specs.
424
425 Will return whether a branch 'fits' the specifications stored in this task.
426
427 Examples:
428 Assuming tot=R40
429 t = Task('Name', 'suite', ['factory', '>=tot-1'])
430 t._fits_spec('factory') # True
431 t._fits_spec('40') # True
432 t._fits_spec('38') # False
433 t._fits_spec('firmware') # False
434
435 Args:
436 branch: the branch to check.
437
438 Returns:
439 True if branch 'fits' with stored specs, False otherwise.
440 """
441 if branch in build_lib.BARE_BRANCHES:
442 return branch in self._bare_branches
443
444 if self._numeric_constraint:
445 if self._version_equal_constraint:
446 return version.LooseVersion(branch) == self._numeric_constraint
447 elif self._version_gte_constraint:
448 return version.LooseVersion(branch) >= self._numeric_constraint
449 elif self._version_lte_constraint:
450 return version.LooseVersion(branch) <= self._numeric_constraint
451 else:
452 return version.LooseVersion(branch) >= self._numeric_constraint
453 else:
454 return False
455
Xinan Lin028f9582019-12-11 10:55:33 -0800456 def _get_firmware_build(self, spec, board, firmware_build_dict, lab_config):
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700457 """Get the firmware build name to test with ChromeOS build.
458
459 Args:
460 spec: a string build spec for RO or RW firmware, eg. firmware,
461 cros. For RO firmware, the value can also be released_ro_X.
462 board: a string board against which this task will run suite job.
Xinan Lin028f9582019-12-11 10:55:33 -0800463 firmware_build_dict: a dict of firmware artifacts, see return value of
464 |base_event.get_firmware_builds|.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700465 lab_config: a config.LabConfig object, to read lab config file.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700466
467 Returns:
468 A string firmware build name.
469
470 Raises:
471 ValueError: if failing to get firmware from lab config file;
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700472 """
473 if not spec or spec == 'stable':
474 # TODO(crbug.com/577316): Query stable RO firmware.
475 logging.debug('%s RO firmware build is not supported.', spec)
476 return None
477
478 try:
Dhanya Ganeshf6014b72020-08-04 22:33:28 +0000479 if firmware_build_dict:
Xinan Lin028f9582019-12-11 10:55:33 -0800480 return firmware_build_dict.get((spec, board), None)
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700481 else:
Xinan Lin028f9582019-12-11 10:55:33 -0800482 return None
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700483 except ValueError as e:
484 logging.warning('Failed to get firmware from lab config file'
485 'for spec %s, board %s: %s', spec, board, str(e))
486 return None
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700487
C Shapiro7f24a002017-12-05 14:25:09 -0700488 def _push_suite(
489 self,
Xinan Lindf0698a2020-02-05 22:38:11 -0800490 task_id=None,
C Shapiro7f24a002017-12-05 14:25:09 -0700491 board=None,
492 model=None,
493 cros_build=None,
494 firmware_rw_build=None,
495 firmware_ro_build=None,
496 test_source_build=None,
497 launch_control_build=None,
Garry Wangdce77572021-07-18 19:33:35 -0700498 run_prod_code=False,
499 secondary_targets=None):
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700500 """Schedule suite job for the task by pushing suites to SuiteQueue.
501
502 Args:
Xinan Lindf0698a2020-02-05 22:38:11 -0800503 task_id: the id to track this task in exectuion.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700504 board: the board against which this suite job run.
C Shapiro7f24a002017-12-05 14:25:09 -0700505 model: the model name for unibuild.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700506 cros_build: the CrOS build of this suite job.
507 firmware_rw_build: Firmware RW build to run this suite job with.
508 firmware_ro_build: Firmware RO build to run this suite job with.
509 test_source_build: Test source build, used for server-side
510 packaging of this suite job.
511 launch_control_build: the launch control build of this suite job.
512 run_prod_code: If True, the suite will run the test code that lives
513 in prod aka the test code currently on the lab servers. If
514 False, the control files and test code for this suite run will
515 be retrieved from the build artifacts. Default is False.
Garry Wangdce77572021-07-18 19:33:35 -0700516 secondary_targets: A list of BuildTarget namedtuple to represent
517 required secondary DUTs info in this suite job run.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700518 """
519 android_build = None
520 testbed_build = None
521
522 if self.testbed_dut_count:
523 launch_control_build = '%s#%d' % (launch_control_build,
524 self.testbed_dut_count)
525 test_source_build = launch_control_build
526 board = '%s-%d' % (board, self.testbed_dut_count)
527
528 if launch_control_build:
529 if not self.testbed_dut_count:
530 android_build = launch_control_build
531 else:
532 testbed_build = launch_control_build
533
Garry Wang111a26f2021-07-23 15:25:14 -0700534 if secondary_targets:
535 secondary_targets = convert_secondary_targets_to_string(secondary_targets)
536 else:
537 secondary_targets = ''
538
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700539 suite_job_parameters = {
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700540 build_lib.BuildVersionKey.ANDROID_BUILD_VERSION: android_build,
Jacob Kopczynskic1d0f5c2020-08-09 10:41:32 -0700541 build_lib.BuildVersionKey.CROS_VERSION: cros_build,
542 build_lib.BuildVersionKey.FW_RO_VERSION: firmware_ro_build,
543 build_lib.BuildVersionKey.FW_RW_VERSION: firmware_rw_build,
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700544 build_lib.BuildVersionKey.TESTBED_BUILD_VERSION: testbed_build,
Taylor Clark8b06a832021-02-16 21:45:42 +0000545 'analytics_name': self.analytics_name,
Jacob Kopczynskic1d0f5c2020-08-09 10:41:32 -0700546 'board': board,
547 'dimensions': self.dimensions,
548 'force': self.force,
549 'job_retry': self.job_retry,
550 'max_runtime_mins': _JOB_MAX_RUNTIME_MINS_DEFAULT,
551 'model': model,
552 'name': self.name,
553 'no_delay': self.no_delay,
554 'no_wait_for_results': not self.job_retry,
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700555 'num': self.num,
556 'pool': self.pool,
557 'priority': self.priority,
Jacob Kopczynskic1d0f5c2020-08-09 10:41:32 -0700558 'qs_account': self.qs_account,
559 'run_prod_code': run_prod_code,
Garry Wangdce77572021-07-18 19:33:35 -0700560 'secondary_targets': secondary_targets,
Jacob Kopczynskic1d0f5c2020-08-09 10:41:32 -0700561 'suite': self.suite,
562 'task_id': task_id,
563 'test_source_build': test_source_build,
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700564 'timeout': self.timeout,
565 'timeout_mins': _JOB_MAX_RUNTIME_MINS_DEFAULT,
Azizur Rahmanb720e512022-05-13 18:30:40 +0000566 'run_via_cft': self.run_via_cft,
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700567 }
568
Xinan Lin9e4917d2019-11-04 10:58:47 -0800569 task_executor.push(task_executor.SUITES_QUEUE,
570 tag=self.suite,
571 **suite_job_parameters)
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700572 logging.info('Pushing task %r into taskqueue', suite_job_parameters)
Xixuan Wu5451a662017-10-17 10:57:40 -0700573 self.is_pushed = True
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700574
Xinan Lin028f9582019-12-11 10:55:33 -0800575 def _schedule_cros_builds(self, build_dict, firmware_build_dict, configs):
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700576 """Schedule tasks with branch builds.
577
578 Args:
Craig Bergstrom58263d32018-04-26 14:11:35 -0600579 build_dict: the build dict for ChromeOS boards, see return
Xinan Linea1efcb2019-12-30 23:46:42 -0800580 value of |build_lib.get_cros_builds|.
Xinan Lin028f9582019-12-11 10:55:33 -0800581 firmware_build_dict: a dict of firmware artifact, see return value of
582 |base_event.get_firmware_builds|.
Xixuan Wuf4a4c882019-03-15 14:48:26 -0700583 configs: A config_reader.Configs object.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700584 """
Xixuan Wuf4a4c882019-03-15 14:48:26 -0700585 lab_config = configs.lab_config
C Shapiro7f24a002017-12-05 14:25:09 -0700586 models_by_board = lab_config.get_cros_model_map() if lab_config else {}
C Shapiro09108252019-08-01 14:52:52 -0500587 model_agnostic_cros_builds = set()
Xixuan Wu8d2f2862018-08-28 16:48:04 -0700588 for (board, passed_model, build_type,
Craig Bergstrom58263d32018-04-26 14:11:35 -0600589 milestone), manifest in build_dict.iteritems():
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700590 cros_build = str(build_lib.CrOSBuild(board, build_type, milestone,
591 manifest))
592 logging.info('Running %s on %s', self.name, cros_build)
Po-Hsien Wang6d589732018-05-15 17:19:34 -0700593 if self.exclude_boards and board in self.exclude_boards:
594 logging.debug('Board %s is in excluded board list: %s',
595 board, self.exclude_boards)
596 continue
Xixuan Wu8d2f2862018-08-28 16:48:04 -0700597
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700598 if self.boards and board not in self.boards:
599 logging.debug('Board %s is not in supported board list: %s',
600 board, self.boards)
601 continue
602
603 # Check the fitness of the build's branch for task
604 branch_build_spec = _pick_branch(build_type, milestone)
605 if not self._fits_spec(branch_build_spec):
Xinan Lindf0698a2020-02-05 22:38:11 -0800606 msg = ("branch_build spec %s doesn't fit this task's "
607 "requirement: %s") % (branch_build_spec,
608 ",".join(self.branch_specs))
609 logging.debug(msg)
610 self.job_section.add_schedule_job(board, passed_model, msg=msg)
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700611 continue
612
Xinan Lindf0698a2020-02-05 22:38:11 -0800613 # Record this build as it matches both board and branch specs.
Jared Loucks7068d8b2022-04-28 09:41:33 -0600614 if self.only_successful_build_required:
Xinan Lindf0698a2020-02-05 22:38:11 -0800615 self.job_section.add_matched_relax_build(
616 board, build_type, milestone, manifest)
617 else:
618 self.job_section.add_matched_build(
619 board, build_type, milestone, manifest)
620
Sean McAllister7378b692021-06-03 15:44:22 -0600621 # check that we only got one spec for ro firmware
622 if all([self.firmware_ro_version, self.firmware_ro_build_spec]):
623 logging.error(
624 "Exactly one of firmware_ro_version or firmware_ro_build_spec " \
625 "allowed, got: %s and %s" % (
626 self.firmware_ro_version,
627 self.firmware_ro_build_spec,
628 ),
629 )
630 continue
631
632 # check that we only got one spec for rw firmware
633 if all([self.firmware_rw_version, self.firmware_rw_build_spec]):
634 logging.error(
635 "Exactly one of firmware_rw_version or firmware_rw_build_spec " \
636 "allowed, got: %s and %s" % (
637 self.firmware_rw_version,
638 self.firmware_rw_build_spec,
639 ),
640 )
641 continue
642
643 # resolve ro firmware version to provision
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700644 firmware_ro_build = None
Brigit Rossbachbb080912020-11-18 13:52:17 -0700645 if self.firmware_ro_version:
646 firmware_ro_build = self.firmware_ro_version
Sean McAllister7378b692021-06-03 15:44:22 -0600647 elif self.firmware_ro_build_spec:
648 firmware_ro_build = self._get_firmware_build(
649 self.firmware_ro_build_spec, board, firmware_build_dict, lab_config)
Brigit Rossbachbb080912020-11-18 13:52:17 -0700650
Sean McAllister7378b692021-06-03 15:44:22 -0600651 if not firmware_ro_build:
652 msg = 'No RO firmware build to run, skip running'
653 logging.debug(msg)
654 self.job_section.add_schedule_job(board, passed_model, msg=msg)
655 continue
656
657 # resolve rw firmware version to provision
658 firmware_rw_build = None
Brigit Rossbachbb080912020-11-18 13:52:17 -0700659 if self.firmware_rw_version:
660 firmware_rw_build = self.firmware_rw_version
Sean McAllister7378b692021-06-03 15:44:22 -0600661 elif self.firmware_rw_build_spec:
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700662 firmware_rw_build = self._get_firmware_build(
Sean McAllister7378b692021-06-03 15:44:22 -0600663 self.firmware_rw_build_spec, board, firmware_build_dict, lab_config)
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700664
Sean McAllister7378b692021-06-03 15:44:22 -0600665 if not firmware_rw_build:
666 msg = 'No RW firmware build to run, skip running'
667 logging.debug(msg)
668 self.job_section.add_schedule_job(board, passed_model, msg=msg)
669 continue
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700670
671 if self.test_source == build_lib.BuildType.FIRMWARE_RW:
672 test_source_build = firmware_rw_build
673 else:
Jacob Kopczynski79d00102018-07-13 15:37:03 -0700674 # Default test source build to CrOS build if it's not specified.
675 # Past versions chose based on run_prod_code, but we no longer respect
676 # that option and scheduler settings should always set it to False.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700677 test_source_build = cros_build
678
Xinan Lindf0698a2020-02-05 22:38:11 -0800679 # Record the matched firwmare build.
680 if firmware_rw_build:
681 self.job_section.add_matched_fw_build(
682 board,
683 self.firmware_rw_build_spec,
684 firmware_rw_build,
685 read_only=False)
686 if firmware_ro_build:
687 self.job_section.add_matched_fw_build(
688 board,
689 self.firmware_ro_build_spec,
690 firmware_ro_build,
691 read_only=True)
692
Xinan Lin6668e0f2020-05-29 10:02:57 -0700693 # Board above is used as build target to control the CrOS image.
694 # The following part is to assign models for lab boards, where
695 # the suffix should be removed.
Prathmesh Prabhu074b8d42020-08-13 17:17:33 +0000696 hwtest_board = build_lib.reshape_board(board)
697
Xinan Lin6668e0f2020-05-29 10:02:57 -0700698 models = models_by_board.get(hwtest_board, [None])
C Shapiro7f24a002017-12-05 14:25:09 -0700699
Harpreet Grewalbbbb7de2019-02-05 19:35:03 +0000700 for model in models:
701 if ((passed_model is not None and model == passed_model) or
702 passed_model is None):
Xinan Lin6668e0f2020-05-29 10:02:57 -0700703 full_model_name = '%s_%s' % (hwtest_board, model)
Xixuan Wua41efa22019-05-17 14:28:04 -0700704 # Respect exclude first.
705 if self.exclude_models and full_model_name in self.exclude_models:
706 logging.debug("Skip model %s as it's in exclude model list %s",
707 model, self.exclude_models)
708 continue
709
710 if self.models and full_model_name not in self.models:
711 logging.debug("Skip model %s as it's not in support model list %s",
712 model, self.models)
713 continue
714
C Shapiro09108252019-08-01 14:52:52 -0500715 explicit_model = model
716
717 if self.any_model:
Xinan Lin3ba18a02019-08-13 15:44:55 -0700718 explicit_model = None
C Shapiro09108252019-08-01 14:52:52 -0500719 unique_build = str(cros_build)
720 if unique_build in model_agnostic_cros_builds:
721 # Skip since we've already run with no explicit model set.
Xinan Lindf0698a2020-02-05 22:38:11 -0800722 msg = "Skip model %s as any_model enabled for this job." % model
723 self.job_section.add_schedule_job(board, model, msg=msg)
C Shapiro09108252019-08-01 14:52:52 -0500724 continue
725 model_agnostic_cros_builds.add(unique_build)
726
Xinan Lindf0698a2020-02-05 22:38:11 -0800727 task_id = str(uuid.uuid1())
Harpreet Grewalbbbb7de2019-02-05 19:35:03 +0000728 self._push_suite(
Xinan Lindf0698a2020-02-05 22:38:11 -0800729 task_id=task_id,
Xixuan Wub4b2f412019-05-03 11:22:31 -0700730 board=hwtest_board,
C Shapiro09108252019-08-01 14:52:52 -0500731 model=explicit_model,
Harpreet Grewalbbbb7de2019-02-05 19:35:03 +0000732 cros_build=cros_build,
733 firmware_rw_build=firmware_rw_build,
734 firmware_ro_build=firmware_ro_build,
Xinan Lin0550f492020-01-21 16:25:53 -0800735 test_source_build=test_source_build)
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700736
Xinan Lin6668e0f2020-05-29 10:02:57 -0700737 # Analytics table stores the build target instead of the lab board.
Xinan Lin0673a182020-04-14 15:09:35 -0700738 self.job_section.add_schedule_job(
739 board, explicit_model, task_id=task_id)
Xinan Lindf0698a2020-02-05 22:38:11 -0800740
Garry Wangdce77572021-07-18 19:33:35 -0700741 def _schedule_multi_duts_cros_builds(self, build_dict,
Garry Wang4e29b512021-08-26 19:32:59 -0700742 recent_build_dict, configs):
Garry Wangdce77572021-07-18 19:33:35 -0700743 """Schedule multi-DUTs tasks with branch builds.
744
745 Args:
746 build_dict: the build dict for ChromeOS boards, see return
747 value of |build_lib.get_cros_builds|.
Garry Wang4e29b512021-08-26 19:32:59 -0700748 recent_build_dict: Same as build_dict, but contains build info
749 with a wider time range.
Garry Wangdce77572021-07-18 19:33:35 -0700750 configs: A config_reader.Configs object.
751 """
752 build_targets_dict = self._get_multi_duts_build_targets_dict(configs)
Garry Wang6ca42dd2022-02-28 21:54:19 -0800753 android_boards_list = configs.lab_config.get_android_model_map().keys()
Garry Wangdce77572021-07-18 19:33:35 -0700754 model_agnostic_builds = set()
755 for (board, passed_model, build_type,
756 milestone), manifest in build_dict.iteritems():
757 cros_build = str(build_lib.CrOSBuild(board, build_type, milestone,
758 manifest))
759 logging.info('Running %s on %s', self.name, cros_build)
760 if board not in build_targets_dict.keys():
761 logging.debug('Board %s is not in primary board list: %s',
762 board, build_targets_dict.keys())
763 continue
764
765 # Check the fitness of the build's branch for task
766 branch_build_spec = _pick_branch(build_type, milestone)
767 if not self._fits_spec(branch_build_spec):
768 msg = ("branch_build spec %s doesn't fit this task's "
769 "requirement: %s") % (branch_build_spec,
770 ",".join(self.branch_specs))
771 logging.debug(msg)
772 self.job_section.add_schedule_job(board, passed_model, msg=msg)
773 continue
774
775 # Record this build as it matches both board and branch specs.
Jared Loucks7068d8b2022-04-28 09:41:33 -0600776 if self.only_successful_build_required:
Garry Wangdce77572021-07-18 19:33:35 -0700777 self.job_section.add_matched_relax_build(
778 board, build_type, milestone, manifest)
779 else:
780 self.job_section.add_matched_build(
781 board, build_type, milestone, manifest)
782
783 # Board above is used as build target to control the CrOS image.
784 # The following part is to assign models for lab boards, where
785 # the suffix should be removed.
786 hwtest_board = build_lib.reshape_board(board)
787 for group in build_targets_dict.get(board):
788 joined_board_strings = '_'.join(dut.board for dut in group)
789 build_tag = '%s_%s' % (joined_board_strings, cros_build)
790 primary_dut = group[0]
791 if primary_dut.model:
792 if primary_dut.model != passed_model:
793 # Explict model specified but not match, so skip to next.
Garry Wang450b6432022-04-21 14:25:32 -0700794 logging.info(
795 "Explict model %s specified but doesn't match with passed"
796 " model %s, skipping.", primary_dut.model, passed_model)
Garry Wangdce77572021-07-18 19:33:35 -0700797 continue
798 elif build_tag in model_agnostic_builds:
799 msg = ("Skip model %s as any_model enabled for this job."
800 % passed_model)
801 self.job_section.add_schedule_job(board, passed_model, msg=msg)
802 continue
803
Garry Wang4e29b512021-08-26 19:32:59 -0700804 # Determine cros builds for secondary DUTs from recent build dict.
Garry Wangdce77572021-07-18 19:33:35 -0700805 secondary_targets = []
806 for s_dut in group[1:]:
Garry Wang6ca42dd2022-02-28 21:54:19 -0800807 if s_dut.board in android_boards_list:
808 # We don't need to provision Android devices for CrOS test.
809 secondary_targets.append(
810 BuildTarget(s_dut.board, s_dut.model, None))
811 continue
Garry Wangdce77572021-07-18 19:33:35 -0700812 s_key = (s_dut.board, None, build_type, milestone)
Garry Wang4e29b512021-08-26 19:32:59 -0700813 s_manifest = recent_build_dict.get(s_key, '')
Garry Wangdce77572021-07-18 19:33:35 -0700814 if s_manifest:
815 s_cros_build = str(build_lib.CrOSBuild(
816 s_dut.board, build_type, milestone, s_manifest))
817 s_hwtest_board = build_lib.reshape_board(s_dut.board)
818 secondary_targets.append(
819 BuildTarget(s_hwtest_board, s_dut.model, s_cros_build))
820 # Check if we get cros build for all secondary DUTs.
821 if len(secondary_targets) != len(group[1:]):
822 logging.info('Cannot determine cros version for all secondary'
823 ' DUTs, skip.')
824 continue
825
826 # Schedule task.
827 model_agnostic_builds.add(build_tag)
828 task_id = str(uuid.uuid1())
829 self._push_suite(
830 task_id = task_id,
831 board=hwtest_board,
832 model=primary_dut.model,
833 cros_build=cros_build,
834 test_source_build=cros_build,
835 secondary_targets=secondary_targets
836 )
837
838 # Analytics table stores the build target instead of the lab board.
839 self.job_section.add_schedule_job(
840 board, primary_dut.model, task_id=task_id)
841
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700842 def _schedule_launch_control_builds(self, launch_control_builds):
843 """Schedule tasks with launch control builds.
844
845 Args:
846 launch_control_builds: the build dict for Android boards.
847 """
848 for board, launch_control_build in launch_control_builds.iteritems():
849 logging.debug('Running %s on %s', self.name, board)
Po-Hsien Wang6d589732018-05-15 17:19:34 -0700850 if self.exclude_boards and board in self.exclude_boards:
851 logging.debug('Board %s is in excluded board list: %s',
852 board, self.exclude_boards)
853 continue
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700854 if self.boards and board not in self.boards:
855 logging.debug('Board %s is not in supported board list: %s',
856 board, self.boards)
857 continue
858
859 for android_build in launch_control_build:
860 if not any([branch in android_build
861 for branch in self.launch_control_branches]):
862 logging.debug('Branch %s is not required to run for task '
863 '%s', android_build, self.name)
864 continue
865
866 self._push_suite(board=board,
867 test_source_build=android_build,
868 launch_control_build=android_build)
869
Garry Wangdce77572021-07-18 19:33:35 -0700870 def _get_multi_duts_build_targets_dict(self, configs):
871 """Generate a dict contains all build targets info based on
872 self.multi_dut_boards or self.multi_dut_models.
873
874 Args:
875 configs: A config_reader.Configs object.
876
877 Returns:
878 A dict where key is a cros board, and value is a list that represents
879 group of DUTs where primary DUT's board equal to key. Each group of
880 DUTs are represented by a list of BuildTarget. Example:
881 {
882 'coral': [
883 [
884 BuildTarget(board='coral', model='babytiger', cros_build=None),
885 BuildTarget(board='eve', model=None, cros_build=None),
886 BuildTarget(board='nami', model='None', cros_build=None)
887 ],
888 [
889 BuildTarget(board='coral', model='blacktiplte', cros_build=None),
890 BuildTarget(board='octopus', model=None, cros_build=None),
891 BuildTarget(board='nami', model=None, cros_build=None)],
892 ]
893 ]
894 }
895 """
896 lab_config = configs.lab_config
897 build_targets = defaultdict(lambda: [])
Garry Wang6ca42dd2022-02-28 21:54:19 -0800898 cros_models_by_board = lab_config.get_cros_model_map() if lab_config else {}
899 android_models_by_board = lab_config.get_android_model_map() if lab_config else {}
900 model_to_board_dict = self._model_to_board_dict(cros_models_by_board)
901 # Multi-DUTs support Android as secondary devices, so we need to update
902 # the dict to include Android boards/models.
903 model_to_board_dict.update(self._model_to_board_dict(android_models_by_board))
904
Garry Wangdce77572021-07-18 19:33:35 -0700905 # Handle the case when multi-DUTs testing requested by board.
906 for group in self.multi_dut_boards:
907 primary_board = group[0]
908 if self.any_model:
909 new_group = [BuildTarget(board, None, None) for board in group]
910 build_targets[primary_board].append(new_group)
911 else:
912 # If any_model disbled, we need to have each model from primary_board
913 # family pair with requested secondary devices.
914 # Board above is used as build target to control the CrOS image.
915 # The following part is to assign models for lab boards, where
916 # the suffix should be removed.
917 hwtest_board = build_lib.reshape_board(primary_board)
Garry Wang6ca42dd2022-02-28 21:54:19 -0800918 models = cros_models_by_board.get(hwtest_board, [None])
Garry Wangdce77572021-07-18 19:33:35 -0700919 for model in models:
920 if self.exclude_models and model in self.exclude_models:
921 logging.info('Model %s is in exclude list, skipping.' % model)
922 continue
923 new_group = []
924 new_group.append(BuildTarget(primary_board, model, None))
925 for board in group[1:]:
926 new_group.append(BuildTarget(board, None, None))
927 build_targets[primary_board].append(new_group)
928 # Handle the case when multi-DUTs testing requested by model.
929 for group in self.multi_dut_models:
930 boards = [model_to_board_dict.get(model, '') for model in group]
931 if '' in boards:
932 logging.info('Cannot find board name from one of requested model,'
933 ' skipping.')
934 continue
935 new_group = [BuildTarget(boards[i], group[i], None) for i in range(len(group))]
936 build_targets[boards[0]].append(new_group)
937 return build_targets
938
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700939
940def _pick_branch(build_type, milestone):
941 """Select branch based on build type.
942
943 If the build_type is a bare branch, return build_type as the build spec.
944 If the build_type is a normal CrOS branch, return milestone as the build
945 spec.
946
947 Args:
948 build_type: a string builder name, like 'release'.
949 milestone: a string milestone, like '55'.
950
951 Returns:
952 A string milestone if build_type represents CrOS build, otherwise
953 return build_type.
954 """
955 return build_type if build_type in build_lib.BARE_BRANCHES else milestone
Xinan Linae7d6372019-09-12 14:42:10 -0700956
957
958def _split_unibuilds(build_dict, configs):
959 """Split the uni-builds to all models under a board.
960
961 Args:
962 build_dict: the build dict for ChromeOS boards, see return
Xinan Linea1efcb2019-12-30 23:46:42 -0800963 value of |build_lib.get_cros_builds|.
Xinan Linae7d6372019-09-12 14:42:10 -0700964 configs: a config_reader.Configs object.
965
966 Returns:
967 A build dict.
968 """
969 models_by_board = configs.lab_config.get_cros_model_map()
970 if not models_by_board:
971 return build_dict
972 all_branch_build_dict = {}
973 for (board, model, config, milestone), platform in build_dict.iteritems():
974 uni_build_models = models_by_board.get(board)
975 if uni_build_models is not None and model is None:
976 for uni_build_model in uni_build_models:
977 model_key = (board, uni_build_model, config, milestone)
978 _add_build_dict(all_branch_build_dict, model_key, platform)
979 continue
980 build_key = (board, model, config, milestone)
981 _add_build_dict(all_branch_build_dict, build_key, platform)
982
983 return all_branch_build_dict
984
985
986def _add_build_dict(build_dict, key, value):
987 """A wrapper to add or update an item in build_dict."""
988 cur_manifest = build_dict.get(key)
989 if cur_manifest is None:
990 build_dict[key] = value
991 return
992 build_dict[key] = max(
993 [cur_manifest, value], key=version.LooseVersion)