blob: bdf2c8e5a8d7e0c8f18ae1aacba517b16ddcd557 [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.
Azizur Rahmana9a189f2022-07-29 20:28:28 +0000128 include_tags, a comma separated list of tags that will be included while
129 finding test cases for CFT test execution path. This will be used only if
130 run_via_cft is set to true.
131 exclude_tags, a comma separated list of tags that will be excluded while
132 finding test cases for CFT test execution path. This will be used only if
133 run_via_cft is set to true.
Jacob Kopczynskic54520f2020-08-07 13:20:12 -0700134
Xixuan Wu83118dd2018-08-27 12:11:35 -0700135 board_family_config: A board family dictionary mapping board_family name
136 to its corresponding boards.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700137 tot: The tot manager for checking ToT. If it's None, a new tot_manager
138 instance will be initialized.
Xinan Lin33937d62020-04-14 14:41:23 -0700139 is_sanity: A boolean; true if we are running in sanity env.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700140 """
Xixuan Wu5451a662017-10-17 10:57:40 -0700141 # Indicate whether there're suites get pushed into taskqueue for this task.
142 self.is_pushed = False
143
Taylor Clark8b06a832021-02-16 21:45:42 +0000144 self.analytics_name = task_info.analytics_name
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700145 self.branch_specs = task_info.branch_specs
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700146 self.cros_build_spec = task_info.cros_build_spec
Xixuan Wu008ee832017-10-12 16:59:34 -0700147 self.day = task_info.day
Jacob Kopczynskic1d0f5c2020-08-09 10:41:32 -0700148 self.firmware_ro_build_spec = task_info.firmware_ro_build_spec
149 self.firmware_rw_build_spec = task_info.firmware_rw_build_spec
Brigit Rossbachbb080912020-11-18 13:52:17 -0700150 self.firmware_ro_version = task_info.firmware_ro_version
151 self.firmware_rw_version = task_info.firmware_rw_version
Jacob Kopczynskic1d0f5c2020-08-09 10:41:32 -0700152 self.force = task_info.force
153 self.frontdoor = task_info.frontdoor
154 self.hour = task_info.hour
155 self.job_retry = task_info.job_retry
156 self.name = task_info.name
157 self.no_delay = task_info.no_delay
158 self.num = task_info.num
Jared Loucks7068d8b2022-04-28 09:41:33 -0600159 self.only_successful_build_required = task_info.only_successful_build_required
Jacob Kopczynskic1d0f5c2020-08-09 10:41:32 -0700160 self.os_type = task_info.os_type
161 self.pool = task_info.pool
162 self.priority = task_info.priority
Xinan Lin4757d6f2020-03-24 22:20:31 -0700163 self.qs_account = task_info.qs_account
Jacob Kopczynskic1d0f5c2020-08-09 10:41:32 -0700164 self.suite = task_info.suite
165 self.test_source = task_info.test_source
166 self.testbed_dut_count = task_info.testbed_dut_count
167 self.timeout = task_info.timeout
Garry Wangdce77572021-07-18 19:33:35 -0700168 self.multi_dut_trigger = task_info.multi_dut_trigger
Azizur Rahmanb720e512022-05-13 18:30:40 +0000169 self.run_via_cft = task_info.run_via_cft
Azizur Rahmana9a189f2022-07-29 20:28:28 +0000170 self.include_tags = task_info.include_tags
171 self.exclude_tags = task_info.exclude_tags
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700172
173 if task_info.lc_branches:
174 self.launch_control_branches = [
175 t.strip() for t in task_info.lc_branches.split(',')]
176 else:
177 self.launch_control_branches = []
178
179 if task_info.lc_targets:
180 self.launch_control_targets = [
181 t.strip() for t in task_info.lc_targets.split(',')]
182 else:
183 self.launch_control_targets = []
184
185 if task_info.boards:
186 self.boards = [t.strip() for t in task_info.boards.split(',')]
187 else:
188 self.boards = []
189
Po-Hsien Wang6d589732018-05-15 17:19:34 -0700190 if task_info.exclude_boards:
191 self.exclude_boards = [
192 t.strip() for t in task_info.exclude_boards.split(',')]
193 else:
194 self.exclude_boards = []
195
Xixuan Wu89897182019-01-03 15:28:01 -0800196 if task_info.models:
197 self.models = [t.strip() for t in task_info.models.split(',')]
198 else:
199 self.models = []
200
201 if task_info.exclude_models:
202 self.exclude_models = [
203 t.strip() for t in task_info.exclude_models.split(',')]
204 else:
205 self.exclude_models = []
206
C Shapiro09108252019-08-01 14:52:52 -0500207 self.any_model = task_info.any_model
208
Po-Hsien Wangdd833072018-08-16 18:09:20 -0700209 if task_info.board_families:
210 # Finetune the allowed boards list with board_families & boards.
211 families = [family.strip()
212 for family in task_info.board_families.split(',')]
213 for family in families:
214 self.boards += board_family_config.get(family, [])
Xixuan Wu89897182019-01-03 15:28:01 -0800215
Po-Hsien Wangdd833072018-08-16 18:09:20 -0700216 if task_info.exclude_board_families:
217 # Finetune the disallowed boards list with exclude_board_families
218 # & exclude_boards.
219 families = [family.strip()
220 for family in task_info.exclude_board_families.split(',')]
221 for family in families:
222 self.exclude_boards += board_family_config.get(family, [])
223
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700224 if tot is None:
225 self.tot_manager = tot_manager.TotMilestoneManager()
226 else:
227 self.tot_manager = tot
228
Xinan Linc8647112020-02-04 16:45:56 -0800229 self.dimensions = task_info.dimensions
230
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700231 self._set_spec_compare_info()
Xinan Lin33937d62020-04-14 14:41:23 -0700232 # Sanity test does not have to upload metrics.
233 if not is_sanity:
234 self.job_section = analytics.ScheduleJobSection(task_info)
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700235
Garry Wangdce77572021-07-18 19:33:35 -0700236 self.is_multi_dut_testing = False
237 if task_info.multi_dut_boards:
238 self.is_multi_dut_testing = True
239 self.multi_dut_boards = [
240 t.strip().split(';') for t in task_info.multi_dut_boards.split(',')]
241 else:
242 self.multi_dut_boards = []
243
244 if task_info.multi_dut_models:
245 self.is_multi_dut_testing = True
246 self.multi_dut_models = [
247 t.strip().split(';') for t in task_info.multi_dut_models.split(',')]
248 else:
249 self.multi_dut_models = []
250
Xinan Lin028f9582019-12-11 10:55:33 -0800251 def schedule(self, launch_control_builds, cros_builds_tuple,
252 firmware_builds, configs):
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700253 """Schedule the task by its settings.
254
255 Args:
256 launch_control_builds: the build dict for Android boards, see
257 return value of |get_launch_control_builds|.
Craig Bergstrom58263d32018-04-26 14:11:35 -0600258 cros_builds_tuple: the two-tuple of build dicts for ChromeOS boards,
259 see return value of |get_cros_builds|.
Xinan Lin028f9582019-12-11 10:55:33 -0800260 firmware_builds: a dict of firmware artifact, see return value of
261 |base_event.get_firmware_builds|.
Xixuan Wuf4a4c882019-03-15 14:48:26 -0700262 configs: a config_reader.Configs object.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700263
264 Raises:
265 SchedulingError: if tasks that should be scheduled fail to schedule.
Xixuan Wu5451a662017-10-17 10:57:40 -0700266
267 Returns:
Jacob Kopczynski79d00102018-07-13 15:37:03 -0700268 A boolean indicator; true if there were any suites related to this
269 task which got pushed into the suites queue.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700270 """
Xixuan Wuf4a4c882019-03-15 14:48:26 -0700271 assert configs.lab_config is not None
Xixuan Wu5451a662017-10-17 10:57:40 -0700272 self.is_pushed = False
273
Craig Bergstrom58263d32018-04-26 14:11:35 -0600274 branch_builds, relaxed_builds = cros_builds_tuple
275 builds_dict = branch_builds
Jared Loucks7068d8b2022-04-28 09:41:33 -0600276 if self.only_successful_build_required:
Xinan Linae7d6372019-09-12 14:42:10 -0700277 builds_dict = _split_unibuilds(relaxed_builds, configs)
Craig Bergstrom58263d32018-04-26 14:11:35 -0600278
Xinan Lindf0698a2020-02-05 22:38:11 -0800279 # Record all target boards and models into job section.
280 lab_config = configs.lab_config
281 models_by_board = lab_config.get_cros_model_map() if lab_config else {}
282 boards = self.boards if self.boards else lab_config.get_cros_board_list()
283 for b in boards:
284 if self.exclude_boards and b in self.exclude_boards:
285 continue
286 self.job_section.add_board(b)
287 models = self.models or models_by_board.get(b, [])
288 for m in models:
289 if m and '%s_%s' % (b, m) not in self.exclude_models:
290 self.job_section.add_model(m)
291
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700292 logging.info('######## Scheduling task %s ########', self.name)
293 if self.os_type == build_lib.OS_TYPE_CROS:
Craig Bergstrom58263d32018-04-26 14:11:35 -0600294 if not builds_dict:
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700295 logging.info('No CrOS build to run, skip running.')
296 else:
Xinan Lin028f9582019-12-11 10:55:33 -0800297 self._schedule_cros_builds(builds_dict, firmware_builds, configs)
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700298 else:
299 if not launch_control_builds:
300 logging.info('No Android build to run, skip running.')
301 else:
302 self._schedule_launch_control_builds(launch_control_builds)
Xinan Lindf0698a2020-02-05 22:38:11 -0800303 upload_result = False
304 try:
305 upload_result = self.job_section.upload()
306 # For any exceptions from BQ, only log it and move on.
307 except Exception as e: #pylint: disable=broad-except
308 logging.exception(str(e))
309 if not upload_result:
310 logging.warning('Failed to insert row: %r', self.job_section)
Xixuan Wu5451a662017-10-17 10:57:40 -0700311 return self.is_pushed
312
Garry Wangdce77572021-07-18 19:33:35 -0700313 def schedule_multi_duts(self, cros_builds_tuple,
Garry Wang4e29b512021-08-26 19:32:59 -0700314 recent_cros_builds_tuple, configs):
Garry Wangdce77572021-07-18 19:33:35 -0700315 """Schedule the multi-DUTs task by its settings.
316
317 Args:
318 cros_builds_tuple: the two-tuple of build dicts for ChromeOS boards,
319 see return value of |get_cros_builds|.
Garry Wang4e29b512021-08-26 19:32:59 -0700320 recent_cros_builds_tuple: Same as cros_builds_tuple, but contains
321 build info with a wider time range.
Garry Wangdce77572021-07-18 19:33:35 -0700322 configs: a config_reader.Configs object.
323
324 Raises:
325 SchedulingError: if tasks that should be scheduled fail to schedule.
326
327 Returns:
328 A boolean indicator; true if there were any suites related to this
329 task which got pushed into the suites queue.
330 """
331 assert configs.lab_config is not None
332 # For multi-DUTs We allow either request by board or request by model,
333 # but not both.
334 if self.multi_dut_boards and self.multi_dut_models:
335 logging.error('Both board and model presented in request but expecting'
336 'only one of them present at a time.')
337 return False
338 self.is_pushed = False
339
340 branch_builds, relaxed_builds = cros_builds_tuple
Garry Wang4e29b512021-08-26 19:32:59 -0700341 recent_branch_builds, recent_relaxed_builds = recent_cros_builds_tuple
Garry Wangdce77572021-07-18 19:33:35 -0700342 builds_dict = branch_builds
Garry Wang4e29b512021-08-26 19:32:59 -0700343 recent_builds_dict = recent_branch_builds
Jared Loucks7068d8b2022-04-28 09:41:33 -0600344 if self.only_successful_build_required:
Garry Wangdce77572021-07-18 19:33:35 -0700345 builds_dict = _split_unibuilds(relaxed_builds, configs)
346 # We don't need to expand build info to model level as
347 # secondary DUTs will have their builds determined by
348 # their board name.
Garry Wang4e29b512021-08-26 19:32:59 -0700349 recent_builds_dict = recent_relaxed_builds
Garry Wangdce77572021-07-18 19:33:35 -0700350
351 # Record multi-DUTs testing only for primary boards as we're
352 # going to have a replacement for SS and analytics layer soon.
353 build_targets_dict = self._get_multi_duts_build_targets_dict(configs)
354 model_set = set()
355 for board, groups in build_targets_dict.items():
356 self.job_section.add_board(board)
357 for group in groups:
358 if group[0].model:
359 model_set.add(group[0].model)
360 for model in model_set:
361 if self.exclude_models and model not in self.exclude_models:
362 self.job_section.add_model(model)
363
364 logging.info('######## Scheduling task %s ########', self.name)
365 if self.os_type != build_lib.OS_TYPE_CROS:
Garry Wang6ca42dd2022-02-28 21:54:19 -0800366 logging.info('Multi-DUTs testing only support CrOS builds.')
Garry Wangdce77572021-07-18 19:33:35 -0700367 return False
368 if not builds_dict:
369 logging.info('No CrOS build to run, skip running.')
370 return False
Garry Wang4e29b512021-08-26 19:32:59 -0700371 if not recent_builds_dict:
372 logging.info('No recent CrOS build for secondary DUTs to run,'
Garry Wangdce77572021-07-18 19:33:35 -0700373 ' skip running.')
374 return False
375 self._schedule_multi_duts_cros_builds(builds_dict,
Garry Wang4e29b512021-08-26 19:32:59 -0700376 recent_builds_dict, configs)
Garry Wangdce77572021-07-18 19:33:35 -0700377
378 upload_result = False
379 try:
380 upload_result = self.job_section.upload()
381 # For any exceptions from BQ, only log it and move on.
382 except Exception as e: #pylint: disable=broad-except
383 logging.exception(str(e))
384 if not upload_result:
385 logging.warning('Failed to insert row: %r', self.job_section)
386 return self.is_pushed
387
388 def _model_to_board_dict(self, models_by_board):
389 """Build a model to board dict based on model by boards dict."""
390 d = {}
391 for board, models in models_by_board.items():
392 for model in models:
393 d[model] = board
394 return d
395
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700396 def _set_spec_compare_info(self):
397 """Set branch spec compare info for task for further check."""
398 self._bare_branches = []
399 self._version_equal_constraint = False
400 self._version_gte_constraint = False
401 self._version_lte_constraint = False
402
403 if not self.branch_specs:
404 # Any milestone is OK.
405 self._numeric_constraint = version.LooseVersion('0')
406 else:
407 self._numeric_constraint = None
408 for spec in self.branch_specs:
409 if 'tot' in spec.lower():
410 # Convert spec >=tot-1 to >=RXX.
411 tot_str = spec[spec.index('tot'):]
412 spec = spec.replace(
413 tot_str, self.tot_manager.convert_tot_spec(tot_str))
414
415 if spec.startswith('>='):
416 self._numeric_constraint = version.LooseVersion(
417 spec.lstrip('>=R'))
418 self._version_gte_constraint = True
419 elif spec.startswith('<='):
420 self._numeric_constraint = version.LooseVersion(
421 spec.lstrip('<=R'))
422 self._version_lte_constraint = True
423 elif spec.startswith('=='):
424 self._version_equal_constraint = True
425 self._numeric_constraint = version.LooseVersion(
426 spec.lstrip('==R'))
427 else:
428 self._bare_branches.append(spec)
429
430 def _fits_spec(self, branch):
431 """Check if a branch is deemed OK by this task's branch specs.
432
433 Will return whether a branch 'fits' the specifications stored in this task.
434
435 Examples:
436 Assuming tot=R40
437 t = Task('Name', 'suite', ['factory', '>=tot-1'])
438 t._fits_spec('factory') # True
439 t._fits_spec('40') # True
440 t._fits_spec('38') # False
441 t._fits_spec('firmware') # False
442
443 Args:
444 branch: the branch to check.
445
446 Returns:
447 True if branch 'fits' with stored specs, False otherwise.
448 """
449 if branch in build_lib.BARE_BRANCHES:
450 return branch in self._bare_branches
451
452 if self._numeric_constraint:
453 if self._version_equal_constraint:
454 return version.LooseVersion(branch) == self._numeric_constraint
455 elif self._version_gte_constraint:
456 return version.LooseVersion(branch) >= self._numeric_constraint
457 elif self._version_lte_constraint:
458 return version.LooseVersion(branch) <= self._numeric_constraint
459 else:
460 return version.LooseVersion(branch) >= self._numeric_constraint
461 else:
462 return False
463
Xinan Lin028f9582019-12-11 10:55:33 -0800464 def _get_firmware_build(self, spec, board, firmware_build_dict, lab_config):
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700465 """Get the firmware build name to test with ChromeOS build.
466
467 Args:
468 spec: a string build spec for RO or RW firmware, eg. firmware,
469 cros. For RO firmware, the value can also be released_ro_X.
470 board: a string board against which this task will run suite job.
Xinan Lin028f9582019-12-11 10:55:33 -0800471 firmware_build_dict: a dict of firmware artifacts, see return value of
472 |base_event.get_firmware_builds|.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700473 lab_config: a config.LabConfig object, to read lab config file.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700474
475 Returns:
476 A string firmware build name.
477
478 Raises:
479 ValueError: if failing to get firmware from lab config file;
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700480 """
481 if not spec or spec == 'stable':
482 # TODO(crbug.com/577316): Query stable RO firmware.
483 logging.debug('%s RO firmware build is not supported.', spec)
484 return None
485
486 try:
Dhanya Ganeshf6014b72020-08-04 22:33:28 +0000487 if firmware_build_dict:
Xinan Lin028f9582019-12-11 10:55:33 -0800488 return firmware_build_dict.get((spec, board), None)
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700489 else:
Xinan Lin028f9582019-12-11 10:55:33 -0800490 return None
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700491 except ValueError as e:
492 logging.warning('Failed to get firmware from lab config file'
493 'for spec %s, board %s: %s', spec, board, str(e))
494 return None
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700495
C Shapiro7f24a002017-12-05 14:25:09 -0700496 def _push_suite(
497 self,
Xinan Lindf0698a2020-02-05 22:38:11 -0800498 task_id=None,
C Shapiro7f24a002017-12-05 14:25:09 -0700499 board=None,
500 model=None,
501 cros_build=None,
502 firmware_rw_build=None,
503 firmware_ro_build=None,
504 test_source_build=None,
505 launch_control_build=None,
Garry Wangdce77572021-07-18 19:33:35 -0700506 run_prod_code=False,
507 secondary_targets=None):
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700508 """Schedule suite job for the task by pushing suites to SuiteQueue.
509
510 Args:
Xinan Lindf0698a2020-02-05 22:38:11 -0800511 task_id: the id to track this task in exectuion.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700512 board: the board against which this suite job run.
C Shapiro7f24a002017-12-05 14:25:09 -0700513 model: the model name for unibuild.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700514 cros_build: the CrOS build of this suite job.
515 firmware_rw_build: Firmware RW build to run this suite job with.
516 firmware_ro_build: Firmware RO build to run this suite job with.
517 test_source_build: Test source build, used for server-side
518 packaging of this suite job.
519 launch_control_build: the launch control build of this suite job.
520 run_prod_code: If True, the suite will run the test code that lives
521 in prod aka the test code currently on the lab servers. If
522 False, the control files and test code for this suite run will
523 be retrieved from the build artifacts. Default is False.
Garry Wangdce77572021-07-18 19:33:35 -0700524 secondary_targets: A list of BuildTarget namedtuple to represent
525 required secondary DUTs info in this suite job run.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700526 """
527 android_build = None
528 testbed_build = None
529
530 if self.testbed_dut_count:
531 launch_control_build = '%s#%d' % (launch_control_build,
532 self.testbed_dut_count)
533 test_source_build = launch_control_build
534 board = '%s-%d' % (board, self.testbed_dut_count)
535
536 if launch_control_build:
537 if not self.testbed_dut_count:
538 android_build = launch_control_build
539 else:
540 testbed_build = launch_control_build
541
Garry Wang111a26f2021-07-23 15:25:14 -0700542 if secondary_targets:
543 secondary_targets = convert_secondary_targets_to_string(secondary_targets)
544 else:
545 secondary_targets = ''
546
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700547 suite_job_parameters = {
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700548 build_lib.BuildVersionKey.ANDROID_BUILD_VERSION: android_build,
Jacob Kopczynskic1d0f5c2020-08-09 10:41:32 -0700549 build_lib.BuildVersionKey.CROS_VERSION: cros_build,
550 build_lib.BuildVersionKey.FW_RO_VERSION: firmware_ro_build,
551 build_lib.BuildVersionKey.FW_RW_VERSION: firmware_rw_build,
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700552 build_lib.BuildVersionKey.TESTBED_BUILD_VERSION: testbed_build,
Taylor Clark8b06a832021-02-16 21:45:42 +0000553 'analytics_name': self.analytics_name,
Jacob Kopczynskic1d0f5c2020-08-09 10:41:32 -0700554 'board': board,
555 'dimensions': self.dimensions,
556 'force': self.force,
557 'job_retry': self.job_retry,
558 'max_runtime_mins': _JOB_MAX_RUNTIME_MINS_DEFAULT,
559 'model': model,
560 'name': self.name,
561 'no_delay': self.no_delay,
562 'no_wait_for_results': not self.job_retry,
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700563 'num': self.num,
564 'pool': self.pool,
565 'priority': self.priority,
Jacob Kopczynskic1d0f5c2020-08-09 10:41:32 -0700566 'qs_account': self.qs_account,
567 'run_prod_code': run_prod_code,
Garry Wangdce77572021-07-18 19:33:35 -0700568 'secondary_targets': secondary_targets,
Jacob Kopczynskic1d0f5c2020-08-09 10:41:32 -0700569 'suite': self.suite,
570 'task_id': task_id,
571 'test_source_build': test_source_build,
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700572 'timeout': self.timeout,
573 'timeout_mins': _JOB_MAX_RUNTIME_MINS_DEFAULT,
Azizur Rahmanb720e512022-05-13 18:30:40 +0000574 'run_via_cft': self.run_via_cft,
Azizur Rahmana9a189f2022-07-29 20:28:28 +0000575 'include_tags': self.include_tags,
576 'exclude_tags': self.exclude_tags,
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700577 }
578
Xinan Lin9e4917d2019-11-04 10:58:47 -0800579 task_executor.push(task_executor.SUITES_QUEUE,
580 tag=self.suite,
581 **suite_job_parameters)
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700582 logging.info('Pushing task %r into taskqueue', suite_job_parameters)
Xixuan Wu5451a662017-10-17 10:57:40 -0700583 self.is_pushed = True
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700584
Xinan Lin028f9582019-12-11 10:55:33 -0800585 def _schedule_cros_builds(self, build_dict, firmware_build_dict, configs):
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700586 """Schedule tasks with branch builds.
587
588 Args:
Craig Bergstrom58263d32018-04-26 14:11:35 -0600589 build_dict: the build dict for ChromeOS boards, see return
Xinan Linea1efcb2019-12-30 23:46:42 -0800590 value of |build_lib.get_cros_builds|.
Xinan Lin028f9582019-12-11 10:55:33 -0800591 firmware_build_dict: a dict of firmware artifact, see return value of
592 |base_event.get_firmware_builds|.
Xixuan Wuf4a4c882019-03-15 14:48:26 -0700593 configs: A config_reader.Configs object.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700594 """
Xixuan Wuf4a4c882019-03-15 14:48:26 -0700595 lab_config = configs.lab_config
C Shapiro7f24a002017-12-05 14:25:09 -0700596 models_by_board = lab_config.get_cros_model_map() if lab_config else {}
C Shapiro09108252019-08-01 14:52:52 -0500597 model_agnostic_cros_builds = set()
Xixuan Wu8d2f2862018-08-28 16:48:04 -0700598 for (board, passed_model, build_type,
Craig Bergstrom58263d32018-04-26 14:11:35 -0600599 milestone), manifest in build_dict.iteritems():
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700600 cros_build = str(build_lib.CrOSBuild(board, build_type, milestone,
601 manifest))
602 logging.info('Running %s on %s', self.name, cros_build)
Po-Hsien Wang6d589732018-05-15 17:19:34 -0700603 if self.exclude_boards and board in self.exclude_boards:
604 logging.debug('Board %s is in excluded board list: %s',
605 board, self.exclude_boards)
606 continue
Xixuan Wu8d2f2862018-08-28 16:48:04 -0700607
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700608 if self.boards and board not in self.boards:
609 logging.debug('Board %s is not in supported board list: %s',
610 board, self.boards)
611 continue
612
613 # Check the fitness of the build's branch for task
614 branch_build_spec = _pick_branch(build_type, milestone)
615 if not self._fits_spec(branch_build_spec):
Xinan Lindf0698a2020-02-05 22:38:11 -0800616 msg = ("branch_build spec %s doesn't fit this task's "
617 "requirement: %s") % (branch_build_spec,
618 ",".join(self.branch_specs))
619 logging.debug(msg)
620 self.job_section.add_schedule_job(board, passed_model, msg=msg)
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700621 continue
622
Xinan Lindf0698a2020-02-05 22:38:11 -0800623 # Record this build as it matches both board and branch specs.
Jared Loucks7068d8b2022-04-28 09:41:33 -0600624 if self.only_successful_build_required:
Xinan Lindf0698a2020-02-05 22:38:11 -0800625 self.job_section.add_matched_relax_build(
626 board, build_type, milestone, manifest)
627 else:
628 self.job_section.add_matched_build(
629 board, build_type, milestone, manifest)
630
Sean McAllister7378b692021-06-03 15:44:22 -0600631 # check that we only got one spec for ro firmware
632 if all([self.firmware_ro_version, self.firmware_ro_build_spec]):
633 logging.error(
634 "Exactly one of firmware_ro_version or firmware_ro_build_spec " \
635 "allowed, got: %s and %s" % (
636 self.firmware_ro_version,
637 self.firmware_ro_build_spec,
638 ),
639 )
640 continue
641
642 # check that we only got one spec for rw firmware
643 if all([self.firmware_rw_version, self.firmware_rw_build_spec]):
644 logging.error(
645 "Exactly one of firmware_rw_version or firmware_rw_build_spec " \
646 "allowed, got: %s and %s" % (
647 self.firmware_rw_version,
648 self.firmware_rw_build_spec,
649 ),
650 )
651 continue
652
653 # resolve ro firmware version to provision
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700654 firmware_ro_build = None
Brigit Rossbachbb080912020-11-18 13:52:17 -0700655 if self.firmware_ro_version:
656 firmware_ro_build = self.firmware_ro_version
Sean McAllister7378b692021-06-03 15:44:22 -0600657 elif self.firmware_ro_build_spec:
658 firmware_ro_build = self._get_firmware_build(
659 self.firmware_ro_build_spec, board, firmware_build_dict, lab_config)
Brigit Rossbachbb080912020-11-18 13:52:17 -0700660
Sean McAllister7378b692021-06-03 15:44:22 -0600661 if not firmware_ro_build:
662 msg = 'No RO firmware build to run, skip running'
663 logging.debug(msg)
664 self.job_section.add_schedule_job(board, passed_model, msg=msg)
665 continue
666
667 # resolve rw firmware version to provision
668 firmware_rw_build = None
Brigit Rossbachbb080912020-11-18 13:52:17 -0700669 if self.firmware_rw_version:
670 firmware_rw_build = self.firmware_rw_version
Sean McAllister7378b692021-06-03 15:44:22 -0600671 elif self.firmware_rw_build_spec:
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700672 firmware_rw_build = self._get_firmware_build(
Sean McAllister7378b692021-06-03 15:44:22 -0600673 self.firmware_rw_build_spec, board, firmware_build_dict, lab_config)
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700674
Sean McAllister7378b692021-06-03 15:44:22 -0600675 if not firmware_rw_build:
676 msg = 'No RW firmware build to run, skip running'
677 logging.debug(msg)
678 self.job_section.add_schedule_job(board, passed_model, msg=msg)
679 continue
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700680
681 if self.test_source == build_lib.BuildType.FIRMWARE_RW:
682 test_source_build = firmware_rw_build
683 else:
Jacob Kopczynski79d00102018-07-13 15:37:03 -0700684 # Default test source build to CrOS build if it's not specified.
685 # Past versions chose based on run_prod_code, but we no longer respect
686 # that option and scheduler settings should always set it to False.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700687 test_source_build = cros_build
688
Xinan Lindf0698a2020-02-05 22:38:11 -0800689 # Record the matched firwmare build.
690 if firmware_rw_build:
691 self.job_section.add_matched_fw_build(
692 board,
693 self.firmware_rw_build_spec,
694 firmware_rw_build,
695 read_only=False)
696 if firmware_ro_build:
697 self.job_section.add_matched_fw_build(
698 board,
699 self.firmware_ro_build_spec,
700 firmware_ro_build,
701 read_only=True)
702
Xinan Lin6668e0f2020-05-29 10:02:57 -0700703 # Board above is used as build target to control the CrOS image.
704 # The following part is to assign models for lab boards, where
705 # the suffix should be removed.
Prathmesh Prabhu074b8d42020-08-13 17:17:33 +0000706 hwtest_board = build_lib.reshape_board(board)
707
Xinan Lin6668e0f2020-05-29 10:02:57 -0700708 models = models_by_board.get(hwtest_board, [None])
C Shapiro7f24a002017-12-05 14:25:09 -0700709
Harpreet Grewalbbbb7de2019-02-05 19:35:03 +0000710 for model in models:
711 if ((passed_model is not None and model == passed_model) or
712 passed_model is None):
Xinan Lin6668e0f2020-05-29 10:02:57 -0700713 full_model_name = '%s_%s' % (hwtest_board, model)
Xixuan Wua41efa22019-05-17 14:28:04 -0700714 # Respect exclude first.
715 if self.exclude_models and full_model_name in self.exclude_models:
716 logging.debug("Skip model %s as it's in exclude model list %s",
717 model, self.exclude_models)
718 continue
719
720 if self.models and full_model_name not in self.models:
721 logging.debug("Skip model %s as it's not in support model list %s",
722 model, self.models)
723 continue
724
C Shapiro09108252019-08-01 14:52:52 -0500725 explicit_model = model
726
727 if self.any_model:
Xinan Lin3ba18a02019-08-13 15:44:55 -0700728 explicit_model = None
C Shapiro09108252019-08-01 14:52:52 -0500729 unique_build = str(cros_build)
730 if unique_build in model_agnostic_cros_builds:
731 # Skip since we've already run with no explicit model set.
Xinan Lindf0698a2020-02-05 22:38:11 -0800732 msg = "Skip model %s as any_model enabled for this job." % model
733 self.job_section.add_schedule_job(board, model, msg=msg)
C Shapiro09108252019-08-01 14:52:52 -0500734 continue
735 model_agnostic_cros_builds.add(unique_build)
736
Xinan Lindf0698a2020-02-05 22:38:11 -0800737 task_id = str(uuid.uuid1())
Harpreet Grewalbbbb7de2019-02-05 19:35:03 +0000738 self._push_suite(
Xinan Lindf0698a2020-02-05 22:38:11 -0800739 task_id=task_id,
Xixuan Wub4b2f412019-05-03 11:22:31 -0700740 board=hwtest_board,
C Shapiro09108252019-08-01 14:52:52 -0500741 model=explicit_model,
Harpreet Grewalbbbb7de2019-02-05 19:35:03 +0000742 cros_build=cros_build,
743 firmware_rw_build=firmware_rw_build,
744 firmware_ro_build=firmware_ro_build,
Xinan Lin0550f492020-01-21 16:25:53 -0800745 test_source_build=test_source_build)
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700746
Xinan Lin6668e0f2020-05-29 10:02:57 -0700747 # Analytics table stores the build target instead of the lab board.
Xinan Lin0673a182020-04-14 15:09:35 -0700748 self.job_section.add_schedule_job(
749 board, explicit_model, task_id=task_id)
Xinan Lindf0698a2020-02-05 22:38:11 -0800750
Garry Wangdce77572021-07-18 19:33:35 -0700751 def _schedule_multi_duts_cros_builds(self, build_dict,
Garry Wang4e29b512021-08-26 19:32:59 -0700752 recent_build_dict, configs):
Garry Wangdce77572021-07-18 19:33:35 -0700753 """Schedule multi-DUTs tasks with branch builds.
754
755 Args:
756 build_dict: the build dict for ChromeOS boards, see return
757 value of |build_lib.get_cros_builds|.
Garry Wang4e29b512021-08-26 19:32:59 -0700758 recent_build_dict: Same as build_dict, but contains build info
759 with a wider time range.
Garry Wangdce77572021-07-18 19:33:35 -0700760 configs: A config_reader.Configs object.
761 """
762 build_targets_dict = self._get_multi_duts_build_targets_dict(configs)
Garry Wang6ca42dd2022-02-28 21:54:19 -0800763 android_boards_list = configs.lab_config.get_android_model_map().keys()
Garry Wangdce77572021-07-18 19:33:35 -0700764 model_agnostic_builds = set()
765 for (board, passed_model, build_type,
766 milestone), manifest in build_dict.iteritems():
767 cros_build = str(build_lib.CrOSBuild(board, build_type, milestone,
768 manifest))
769 logging.info('Running %s on %s', self.name, cros_build)
770 if board not in build_targets_dict.keys():
771 logging.debug('Board %s is not in primary board list: %s',
772 board, build_targets_dict.keys())
773 continue
774
775 # Check the fitness of the build's branch for task
776 branch_build_spec = _pick_branch(build_type, milestone)
777 if not self._fits_spec(branch_build_spec):
778 msg = ("branch_build spec %s doesn't fit this task's "
779 "requirement: %s") % (branch_build_spec,
780 ",".join(self.branch_specs))
781 logging.debug(msg)
782 self.job_section.add_schedule_job(board, passed_model, msg=msg)
783 continue
784
785 # Record this build as it matches both board and branch specs.
Jared Loucks7068d8b2022-04-28 09:41:33 -0600786 if self.only_successful_build_required:
Garry Wangdce77572021-07-18 19:33:35 -0700787 self.job_section.add_matched_relax_build(
788 board, build_type, milestone, manifest)
789 else:
790 self.job_section.add_matched_build(
791 board, build_type, milestone, manifest)
792
793 # Board above is used as build target to control the CrOS image.
794 # The following part is to assign models for lab boards, where
795 # the suffix should be removed.
796 hwtest_board = build_lib.reshape_board(board)
797 for group in build_targets_dict.get(board):
798 joined_board_strings = '_'.join(dut.board for dut in group)
799 build_tag = '%s_%s' % (joined_board_strings, cros_build)
800 primary_dut = group[0]
801 if primary_dut.model:
802 if primary_dut.model != passed_model:
803 # Explict model specified but not match, so skip to next.
Garry Wang450b6432022-04-21 14:25:32 -0700804 logging.info(
805 "Explict model %s specified but doesn't match with passed"
806 " model %s, skipping.", primary_dut.model, passed_model)
Garry Wangdce77572021-07-18 19:33:35 -0700807 continue
808 elif build_tag in model_agnostic_builds:
809 msg = ("Skip model %s as any_model enabled for this job."
810 % passed_model)
811 self.job_section.add_schedule_job(board, passed_model, msg=msg)
812 continue
813
Garry Wang4e29b512021-08-26 19:32:59 -0700814 # Determine cros builds for secondary DUTs from recent build dict.
Garry Wangdce77572021-07-18 19:33:35 -0700815 secondary_targets = []
816 for s_dut in group[1:]:
Garry Wang6ca42dd2022-02-28 21:54:19 -0800817 if s_dut.board in android_boards_list:
818 # We don't need to provision Android devices for CrOS test.
819 secondary_targets.append(
820 BuildTarget(s_dut.board, s_dut.model, None))
821 continue
Garry Wangdce77572021-07-18 19:33:35 -0700822 s_key = (s_dut.board, None, build_type, milestone)
Garry Wang4e29b512021-08-26 19:32:59 -0700823 s_manifest = recent_build_dict.get(s_key, '')
Garry Wangdce77572021-07-18 19:33:35 -0700824 if s_manifest:
825 s_cros_build = str(build_lib.CrOSBuild(
826 s_dut.board, build_type, milestone, s_manifest))
827 s_hwtest_board = build_lib.reshape_board(s_dut.board)
828 secondary_targets.append(
829 BuildTarget(s_hwtest_board, s_dut.model, s_cros_build))
830 # Check if we get cros build for all secondary DUTs.
831 if len(secondary_targets) != len(group[1:]):
832 logging.info('Cannot determine cros version for all secondary'
833 ' DUTs, skip.')
834 continue
835
836 # Schedule task.
837 model_agnostic_builds.add(build_tag)
838 task_id = str(uuid.uuid1())
839 self._push_suite(
840 task_id = task_id,
841 board=hwtest_board,
842 model=primary_dut.model,
843 cros_build=cros_build,
844 test_source_build=cros_build,
845 secondary_targets=secondary_targets
846 )
847
848 # Analytics table stores the build target instead of the lab board.
849 self.job_section.add_schedule_job(
850 board, primary_dut.model, task_id=task_id)
851
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700852 def _schedule_launch_control_builds(self, launch_control_builds):
853 """Schedule tasks with launch control builds.
854
855 Args:
856 launch_control_builds: the build dict for Android boards.
857 """
858 for board, launch_control_build in launch_control_builds.iteritems():
859 logging.debug('Running %s on %s', self.name, board)
Po-Hsien Wang6d589732018-05-15 17:19:34 -0700860 if self.exclude_boards and board in self.exclude_boards:
861 logging.debug('Board %s is in excluded board list: %s',
862 board, self.exclude_boards)
863 continue
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700864 if self.boards and board not in self.boards:
865 logging.debug('Board %s is not in supported board list: %s',
866 board, self.boards)
867 continue
868
869 for android_build in launch_control_build:
870 if not any([branch in android_build
871 for branch in self.launch_control_branches]):
872 logging.debug('Branch %s is not required to run for task '
873 '%s', android_build, self.name)
874 continue
875
876 self._push_suite(board=board,
877 test_source_build=android_build,
878 launch_control_build=android_build)
879
Garry Wangdce77572021-07-18 19:33:35 -0700880 def _get_multi_duts_build_targets_dict(self, configs):
881 """Generate a dict contains all build targets info based on
882 self.multi_dut_boards or self.multi_dut_models.
883
884 Args:
885 configs: A config_reader.Configs object.
886
887 Returns:
888 A dict where key is a cros board, and value is a list that represents
889 group of DUTs where primary DUT's board equal to key. Each group of
890 DUTs are represented by a list of BuildTarget. Example:
891 {
892 'coral': [
893 [
894 BuildTarget(board='coral', model='babytiger', cros_build=None),
895 BuildTarget(board='eve', model=None, cros_build=None),
896 BuildTarget(board='nami', model='None', cros_build=None)
897 ],
898 [
899 BuildTarget(board='coral', model='blacktiplte', cros_build=None),
900 BuildTarget(board='octopus', model=None, cros_build=None),
901 BuildTarget(board='nami', model=None, cros_build=None)],
902 ]
903 ]
904 }
905 """
906 lab_config = configs.lab_config
907 build_targets = defaultdict(lambda: [])
Garry Wang6ca42dd2022-02-28 21:54:19 -0800908 cros_models_by_board = lab_config.get_cros_model_map() if lab_config else {}
909 android_models_by_board = lab_config.get_android_model_map() if lab_config else {}
910 model_to_board_dict = self._model_to_board_dict(cros_models_by_board)
911 # Multi-DUTs support Android as secondary devices, so we need to update
912 # the dict to include Android boards/models.
913 model_to_board_dict.update(self._model_to_board_dict(android_models_by_board))
914
Garry Wangdce77572021-07-18 19:33:35 -0700915 # Handle the case when multi-DUTs testing requested by board.
916 for group in self.multi_dut_boards:
917 primary_board = group[0]
918 if self.any_model:
919 new_group = [BuildTarget(board, None, None) for board in group]
920 build_targets[primary_board].append(new_group)
921 else:
922 # If any_model disbled, we need to have each model from primary_board
923 # family pair with requested secondary devices.
924 # Board above is used as build target to control the CrOS image.
925 # The following part is to assign models for lab boards, where
926 # the suffix should be removed.
927 hwtest_board = build_lib.reshape_board(primary_board)
Garry Wang6ca42dd2022-02-28 21:54:19 -0800928 models = cros_models_by_board.get(hwtest_board, [None])
Garry Wangdce77572021-07-18 19:33:35 -0700929 for model in models:
930 if self.exclude_models and model in self.exclude_models:
931 logging.info('Model %s is in exclude list, skipping.' % model)
932 continue
933 new_group = []
934 new_group.append(BuildTarget(primary_board, model, None))
935 for board in group[1:]:
936 new_group.append(BuildTarget(board, None, None))
937 build_targets[primary_board].append(new_group)
938 # Handle the case when multi-DUTs testing requested by model.
939 for group in self.multi_dut_models:
940 boards = [model_to_board_dict.get(model, '') for model in group]
941 if '' in boards:
942 logging.info('Cannot find board name from one of requested model,'
943 ' skipping.')
944 continue
945 new_group = [BuildTarget(boards[i], group[i], None) for i in range(len(group))]
946 build_targets[boards[0]].append(new_group)
947 return build_targets
948
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700949
950def _pick_branch(build_type, milestone):
951 """Select branch based on build type.
952
953 If the build_type is a bare branch, return build_type as the build spec.
954 If the build_type is a normal CrOS branch, return milestone as the build
955 spec.
956
957 Args:
958 build_type: a string builder name, like 'release'.
959 milestone: a string milestone, like '55'.
960
961 Returns:
962 A string milestone if build_type represents CrOS build, otherwise
963 return build_type.
964 """
965 return build_type if build_type in build_lib.BARE_BRANCHES else milestone
Xinan Linae7d6372019-09-12 14:42:10 -0700966
967
968def _split_unibuilds(build_dict, configs):
969 """Split the uni-builds to all models under a board.
970
971 Args:
972 build_dict: the build dict for ChromeOS boards, see return
Xinan Linea1efcb2019-12-30 23:46:42 -0800973 value of |build_lib.get_cros_builds|.
Xinan Linae7d6372019-09-12 14:42:10 -0700974 configs: a config_reader.Configs object.
975
976 Returns:
977 A build dict.
978 """
979 models_by_board = configs.lab_config.get_cros_model_map()
980 if not models_by_board:
981 return build_dict
982 all_branch_build_dict = {}
983 for (board, model, config, milestone), platform in build_dict.iteritems():
984 uni_build_models = models_by_board.get(board)
985 if uni_build_models is not None and model is None:
986 for uni_build_model in uni_build_models:
987 model_key = (board, uni_build_model, config, milestone)
988 _add_build_dict(all_branch_build_dict, model_key, platform)
989 continue
990 build_key = (board, model, config, milestone)
991 _add_build_dict(all_branch_build_dict, build_key, platform)
992
993 return all_branch_build_dict
994
995
996def _add_build_dict(build_dict, key, value):
997 """A wrapper to add or update an item in build_dict."""
998 cur_manifest = build_dict.get(key)
999 if cur_manifest is None:
1000 build_dict[key] = value
1001 return
1002 build_dict[key] = max(
1003 [cur_manifest, value], key=version.LooseVersion)