blob: 73cd9998d939b22da316013ed86bf29d8d92270f [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
Xixuan Wu0c76d5b2017-08-30 16:40:17 -07009import logging
Xinan Lindf0698a2020-02-05 22:38:11 -080010import uuid
Xixuan Wu0c76d5b2017-08-30 16:40:17 -070011
Xinan Lindf0698a2020-02-05 22:38:11 -080012import analytics
Xixuan Wu0c76d5b2017-08-30 16:40:17 -070013import build_lib
14import task_executor
Xixuan Wu0c76d5b2017-08-30 16:40:17 -070015import tot_manager
16
17# The max lifetime of a suite scheduled by suite scheduler
Sean Abrahamec0d0762020-09-18 17:19:05 +000018_JOB_MAX_RUNTIME_MINS_DEFAULT = 72 * 60
Xixuan Wu0c76d5b2017-08-30 16:40:17 -070019
Xixuan Wu0c76d5b2017-08-30 16:40:17 -070020
21class SchedulingError(Exception):
22 """Raised to indicate a failure in scheduling a task."""
23
24
25class Task(object):
26 """Represents an entry from the suite_scheduler config file.
27
28 Each entry from the suite_scheduler config file maps one-to-one to a
29 Task. Each instance has enough information to schedule itself.
30 """
31
Xinan Lin33937d62020-04-14 14:41:23 -070032 def __init__(self,
33 task_info,
34 board_family_config={},
35 tot=None,
36 is_sanity=False):
Xixuan Wu0c76d5b2017-08-30 16:40:17 -070037 """Initialize a task instance.
38
39 Args:
40 task_info: a config_reader.TaskInfo object, which includes:
41 name, name of this task, e.g. 'NightlyPower'
42 suite, the name of the suite to run, e.g. 'graphics_per-day'
43 branch_specs, a pre-vetted iterable of branch specifiers,
44 e.g. ['>=R18', 'factory']
45 pool, the pool of machines to schedule tasks. Default is None.
46 num, the number of devices to shard the test suite. It could
47 be an Integer or None. By default it's None.
Po-Hsien Wangdd833072018-08-16 18:09:20 -070048 board_families, a common separated list of board family to run this
49 task on. Boards belong to one of the board family in this list
50 would be added to task_info.boards.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -070051 boards, a comma separated list of boards to run this task on. Default
Po-Hsien Wangdd833072018-08-16 18:09:20 -070052 is None, which allows this task to run on all boards. If same board
53 is specified in 'boards' and 'exclude_boards', we exclude this
54 board.
Xinan Linc8647112020-02-04 16:45:56 -080055 dimensions, a comma separated lists of labels. Each label is in
56 the form of 'key:value'.
Po-Hsien Wangdd833072018-08-16 18:09:20 -070057 exclude_board_families, a common separated list of board family not to
58 run task on. Boards belong to one of the board family in this list
59 would be added to task_info.exclude_boards.
Po-Hsien Wang6d589732018-05-15 17:19:34 -070060 exclude_boards, a comma separated list of boards not to run this task
61 on. Default is None, which allows this task to run on all boards.
Po-Hsien Wangdd833072018-08-16 18:09:20 -070062 If same board is specified in 'boards' and 'exclude_boards', we
63 exclude this board.
Xixuan Wu89897182019-01-03 15:28:01 -080064 models, a comma separated list of models to run this task on. Default
65 is None, which allows this task to run on all models. If same model
66 is specified in 'models' and 'exclude_models', we exclude this
67 model.
68 exclude_models, a comma separated list of models not to run this task
69 on. Default is None, which allows this task to run on all models.
C Shapiro09108252019-08-01 14:52:52 -050070 any_model, set to True to not pass the model parameter and allow
71 a test suite to run any/all models available for testing.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -070072 priority, the string name of a priority from constants.Priorities.
73 timeout, the max lifetime of the suite in hours.
74 cros_build_spec, spec used to determine the ChromeOS build to test
75 with a firmware build, e.g., tot, R41 etc.
76 firmware_rw_build_spec, spec used to determine the firmware RW build
77 test with a ChromeOS build.
78 firmware_ro_build_spec, spec used to determine the firmware RO build
79 test with a ChromeOS build.
80 test_source, the source of test code when firmware will be updated in
81 the test. The value can be 'firmware_rw', 'firmware_ro' or 'cros'.
82 job_retry, set to True to enable job-level retry. Default is False.
Xixuan Wu80531932017-10-12 17:26:51 -070083 no_delay, set to True to raise the priority of this task in task.
84 force, set to True to schedule this suite no matter whether there's
85 duplicate jobs before.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -070086 queue, so the suite jobs can start running tests with no waiting.
87 hour, an integer specifying the hour that a nightly run should be
88 triggered, default is set to 21.
89 day, an integer specifying the day of a week that a weekly run should
90 be triggered, default is set to 5 (Saturday).
91 os_type, type of OS, e.g., cros, brillo, android. Default is cros.
92 The argument is required for android/brillo builds.
93 launch_control_branches, comma separated string of launch control
94 branches. The argument is required and only applicable for
95 android/brillo builds.
96 launch_control_targets, comma separated string of build targets for
97 launch control builds. The argument is required and only
98 applicable for android/brillo builds.
99 testbed_dut_count, number of duts to test when using a testbed.
Xinan Lin4757d6f2020-03-24 22:20:31 -0700100 qs_account, quota account for the unmanaged pool which has enabled
101 Quota Scheduler.
Jacob Kopczynskic54520f2020-08-07 13:20:12 -0700102
Xixuan Wu83118dd2018-08-27 12:11:35 -0700103 board_family_config: A board family dictionary mapping board_family name
104 to its corresponding boards.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700105 tot: The tot manager for checking ToT. If it's None, a new tot_manager
106 instance will be initialized.
Xinan Lin33937d62020-04-14 14:41:23 -0700107 is_sanity: A boolean; true if we are running in sanity env.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700108 """
Xixuan Wu5451a662017-10-17 10:57:40 -0700109 # Indicate whether there're suites get pushed into taskqueue for this task.
110 self.is_pushed = False
111
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700112 self.branch_specs = task_info.branch_specs
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700113 self.cros_build_spec = task_info.cros_build_spec
Xixuan Wu008ee832017-10-12 16:59:34 -0700114 self.day = task_info.day
Jacob Kopczynskic1d0f5c2020-08-09 10:41:32 -0700115 self.firmware_ro_build_spec = task_info.firmware_ro_build_spec
116 self.firmware_rw_build_spec = task_info.firmware_rw_build_spec
117 self.force = task_info.force
118 self.frontdoor = task_info.frontdoor
119 self.hour = task_info.hour
120 self.job_retry = task_info.job_retry
121 self.name = task_info.name
122 self.no_delay = task_info.no_delay
123 self.num = task_info.num
Craig Bergstrom58263d32018-04-26 14:11:35 -0600124 self.only_hwtest_sanity_required = task_info.only_hwtest_sanity_required
Jacob Kopczynskic1d0f5c2020-08-09 10:41:32 -0700125 self.os_type = task_info.os_type
126 self.pool = task_info.pool
127 self.priority = task_info.priority
Xinan Lin4757d6f2020-03-24 22:20:31 -0700128 self.qs_account = task_info.qs_account
Jacob Kopczynskic1d0f5c2020-08-09 10:41:32 -0700129 self.suite = task_info.suite
130 self.test_source = task_info.test_source
131 self.testbed_dut_count = task_info.testbed_dut_count
132 self.timeout = task_info.timeout
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700133
134 if task_info.lc_branches:
135 self.launch_control_branches = [
136 t.strip() for t in task_info.lc_branches.split(',')]
137 else:
138 self.launch_control_branches = []
139
140 if task_info.lc_targets:
141 self.launch_control_targets = [
142 t.strip() for t in task_info.lc_targets.split(',')]
143 else:
144 self.launch_control_targets = []
145
146 if task_info.boards:
147 self.boards = [t.strip() for t in task_info.boards.split(',')]
148 else:
149 self.boards = []
150
Po-Hsien Wang6d589732018-05-15 17:19:34 -0700151 if task_info.exclude_boards:
152 self.exclude_boards = [
153 t.strip() for t in task_info.exclude_boards.split(',')]
154 else:
155 self.exclude_boards = []
156
Xixuan Wu89897182019-01-03 15:28:01 -0800157 if task_info.models:
158 self.models = [t.strip() for t in task_info.models.split(',')]
159 else:
160 self.models = []
161
162 if task_info.exclude_models:
163 self.exclude_models = [
164 t.strip() for t in task_info.exclude_models.split(',')]
165 else:
166 self.exclude_models = []
167
C Shapiro09108252019-08-01 14:52:52 -0500168 self.any_model = task_info.any_model
169
Po-Hsien Wangdd833072018-08-16 18:09:20 -0700170 if task_info.board_families:
171 # Finetune the allowed boards list with board_families & boards.
172 families = [family.strip()
173 for family in task_info.board_families.split(',')]
174 for family in families:
175 self.boards += board_family_config.get(family, [])
Xixuan Wu89897182019-01-03 15:28:01 -0800176
Po-Hsien Wangdd833072018-08-16 18:09:20 -0700177 if task_info.exclude_board_families:
178 # Finetune the disallowed boards list with exclude_board_families
179 # & exclude_boards.
180 families = [family.strip()
181 for family in task_info.exclude_board_families.split(',')]
182 for family in families:
183 self.exclude_boards += board_family_config.get(family, [])
184
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700185 if tot is None:
186 self.tot_manager = tot_manager.TotMilestoneManager()
187 else:
188 self.tot_manager = tot
189
Xinan Linc8647112020-02-04 16:45:56 -0800190 self.dimensions = task_info.dimensions
191
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700192 self._set_spec_compare_info()
Xinan Lin33937d62020-04-14 14:41:23 -0700193 # Sanity test does not have to upload metrics.
194 if not is_sanity:
195 self.job_section = analytics.ScheduleJobSection(task_info)
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700196
Xinan Lin028f9582019-12-11 10:55:33 -0800197 def schedule(self, launch_control_builds, cros_builds_tuple,
198 firmware_builds, configs):
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700199 """Schedule the task by its settings.
200
201 Args:
202 launch_control_builds: the build dict for Android boards, see
203 return value of |get_launch_control_builds|.
Craig Bergstrom58263d32018-04-26 14:11:35 -0600204 cros_builds_tuple: the two-tuple of build dicts for ChromeOS boards,
205 see return value of |get_cros_builds|.
Xinan Lin028f9582019-12-11 10:55:33 -0800206 firmware_builds: a dict of firmware artifact, see return value of
207 |base_event.get_firmware_builds|.
Xixuan Wuf4a4c882019-03-15 14:48:26 -0700208 configs: a config_reader.Configs object.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700209
210 Raises:
211 SchedulingError: if tasks that should be scheduled fail to schedule.
Xixuan Wu5451a662017-10-17 10:57:40 -0700212
213 Returns:
Jacob Kopczynski79d00102018-07-13 15:37:03 -0700214 A boolean indicator; true if there were any suites related to this
215 task which got pushed into the suites queue.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700216 """
Xixuan Wuf4a4c882019-03-15 14:48:26 -0700217 assert configs.lab_config is not None
Xixuan Wu5451a662017-10-17 10:57:40 -0700218 self.is_pushed = False
219
Craig Bergstrom58263d32018-04-26 14:11:35 -0600220 branch_builds, relaxed_builds = cros_builds_tuple
221 builds_dict = branch_builds
222 if self.only_hwtest_sanity_required:
Xinan Linae7d6372019-09-12 14:42:10 -0700223 builds_dict = _split_unibuilds(relaxed_builds, configs)
Craig Bergstrom58263d32018-04-26 14:11:35 -0600224
Xinan Lindf0698a2020-02-05 22:38:11 -0800225 # Record all target boards and models into job section.
226 lab_config = configs.lab_config
227 models_by_board = lab_config.get_cros_model_map() if lab_config else {}
228 boards = self.boards if self.boards else lab_config.get_cros_board_list()
229 for b in boards:
230 if self.exclude_boards and b in self.exclude_boards:
231 continue
232 self.job_section.add_board(b)
233 models = self.models or models_by_board.get(b, [])
234 for m in models:
235 if m and '%s_%s' % (b, m) not in self.exclude_models:
236 self.job_section.add_model(m)
237
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700238 logging.info('######## Scheduling task %s ########', self.name)
239 if self.os_type == build_lib.OS_TYPE_CROS:
Craig Bergstrom58263d32018-04-26 14:11:35 -0600240 if not builds_dict:
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700241 logging.info('No CrOS build to run, skip running.')
242 else:
Xinan Lin028f9582019-12-11 10:55:33 -0800243 self._schedule_cros_builds(builds_dict, firmware_builds, configs)
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700244 else:
245 if not launch_control_builds:
246 logging.info('No Android build to run, skip running.')
247 else:
248 self._schedule_launch_control_builds(launch_control_builds)
Xinan Lindf0698a2020-02-05 22:38:11 -0800249 upload_result = False
250 try:
251 upload_result = self.job_section.upload()
252 # For any exceptions from BQ, only log it and move on.
253 except Exception as e: #pylint: disable=broad-except
254 logging.exception(str(e))
255 if not upload_result:
256 logging.warning('Failed to insert row: %r', self.job_section)
Xixuan Wu5451a662017-10-17 10:57:40 -0700257 return self.is_pushed
258
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700259 def _set_spec_compare_info(self):
260 """Set branch spec compare info for task for further check."""
261 self._bare_branches = []
262 self._version_equal_constraint = False
263 self._version_gte_constraint = False
264 self._version_lte_constraint = False
265
266 if not self.branch_specs:
267 # Any milestone is OK.
268 self._numeric_constraint = version.LooseVersion('0')
269 else:
270 self._numeric_constraint = None
271 for spec in self.branch_specs:
272 if 'tot' in spec.lower():
273 # Convert spec >=tot-1 to >=RXX.
274 tot_str = spec[spec.index('tot'):]
275 spec = spec.replace(
276 tot_str, self.tot_manager.convert_tot_spec(tot_str))
277
278 if spec.startswith('>='):
279 self._numeric_constraint = version.LooseVersion(
280 spec.lstrip('>=R'))
281 self._version_gte_constraint = True
282 elif spec.startswith('<='):
283 self._numeric_constraint = version.LooseVersion(
284 spec.lstrip('<=R'))
285 self._version_lte_constraint = True
286 elif spec.startswith('=='):
287 self._version_equal_constraint = True
288 self._numeric_constraint = version.LooseVersion(
289 spec.lstrip('==R'))
290 else:
291 self._bare_branches.append(spec)
292
293 def _fits_spec(self, branch):
294 """Check if a branch is deemed OK by this task's branch specs.
295
296 Will return whether a branch 'fits' the specifications stored in this task.
297
298 Examples:
299 Assuming tot=R40
300 t = Task('Name', 'suite', ['factory', '>=tot-1'])
301 t._fits_spec('factory') # True
302 t._fits_spec('40') # True
303 t._fits_spec('38') # False
304 t._fits_spec('firmware') # False
305
306 Args:
307 branch: the branch to check.
308
309 Returns:
310 True if branch 'fits' with stored specs, False otherwise.
311 """
312 if branch in build_lib.BARE_BRANCHES:
313 return branch in self._bare_branches
314
315 if self._numeric_constraint:
316 if self._version_equal_constraint:
317 return version.LooseVersion(branch) == self._numeric_constraint
318 elif self._version_gte_constraint:
319 return version.LooseVersion(branch) >= self._numeric_constraint
320 elif self._version_lte_constraint:
321 return version.LooseVersion(branch) <= self._numeric_constraint
322 else:
323 return version.LooseVersion(branch) >= self._numeric_constraint
324 else:
325 return False
326
Xinan Lin028f9582019-12-11 10:55:33 -0800327 def _get_firmware_build(self, spec, board, firmware_build_dict, lab_config):
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700328 """Get the firmware build name to test with ChromeOS build.
329
330 Args:
331 spec: a string build spec for RO or RW firmware, eg. firmware,
332 cros. For RO firmware, the value can also be released_ro_X.
333 board: a string board against which this task will run suite job.
Xinan Lin028f9582019-12-11 10:55:33 -0800334 firmware_build_dict: a dict of firmware artifacts, see return value of
335 |base_event.get_firmware_builds|.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700336 lab_config: a config.LabConfig object, to read lab config file.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700337
338 Returns:
339 A string firmware build name.
340
341 Raises:
342 ValueError: if failing to get firmware from lab config file;
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700343 """
344 if not spec or spec == 'stable':
345 # TODO(crbug.com/577316): Query stable RO firmware.
346 logging.debug('%s RO firmware build is not supported.', spec)
347 return None
348
349 try:
Dhanya Ganeshf6014b72020-08-04 22:33:28 +0000350 if firmware_build_dict:
Xinan Lin028f9582019-12-11 10:55:33 -0800351 return firmware_build_dict.get((spec, board), None)
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700352 else:
Xinan Lin028f9582019-12-11 10:55:33 -0800353 return None
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700354 except ValueError as e:
355 logging.warning('Failed to get firmware from lab config file'
356 'for spec %s, board %s: %s', spec, board, str(e))
357 return None
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700358
C Shapiro7f24a002017-12-05 14:25:09 -0700359 def _push_suite(
360 self,
Xinan Lindf0698a2020-02-05 22:38:11 -0800361 task_id=None,
C Shapiro7f24a002017-12-05 14:25:09 -0700362 board=None,
363 model=None,
364 cros_build=None,
365 firmware_rw_build=None,
366 firmware_ro_build=None,
367 test_source_build=None,
368 launch_control_build=None,
Xinan Lin4757d6f2020-03-24 22:20:31 -0700369 run_prod_code=False):
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700370 """Schedule suite job for the task by pushing suites to SuiteQueue.
371
372 Args:
Xinan Lindf0698a2020-02-05 22:38:11 -0800373 task_id: the id to track this task in exectuion.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700374 board: the board against which this suite job run.
C Shapiro7f24a002017-12-05 14:25:09 -0700375 model: the model name for unibuild.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700376 cros_build: the CrOS build of this suite job.
377 firmware_rw_build: Firmware RW build to run this suite job with.
378 firmware_ro_build: Firmware RO build to run this suite job with.
379 test_source_build: Test source build, used for server-side
380 packaging of this suite job.
381 launch_control_build: the launch control build of this suite job.
382 run_prod_code: If True, the suite will run the test code that lives
383 in prod aka the test code currently on the lab servers. If
384 False, the control files and test code for this suite run will
385 be retrieved from the build artifacts. Default is False.
386 """
387 android_build = None
388 testbed_build = None
389
390 if self.testbed_dut_count:
391 launch_control_build = '%s#%d' % (launch_control_build,
392 self.testbed_dut_count)
393 test_source_build = launch_control_build
394 board = '%s-%d' % (board, self.testbed_dut_count)
395
396 if launch_control_build:
397 if not self.testbed_dut_count:
398 android_build = launch_control_build
399 else:
400 testbed_build = launch_control_build
401
402 suite_job_parameters = {
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700403 build_lib.BuildVersionKey.ANDROID_BUILD_VERSION: android_build,
Jacob Kopczynskic1d0f5c2020-08-09 10:41:32 -0700404 build_lib.BuildVersionKey.CROS_VERSION: cros_build,
405 build_lib.BuildVersionKey.FW_RO_VERSION: firmware_ro_build,
406 build_lib.BuildVersionKey.FW_RW_VERSION: firmware_rw_build,
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700407 build_lib.BuildVersionKey.TESTBED_BUILD_VERSION: testbed_build,
Jacob Kopczynskic1d0f5c2020-08-09 10:41:32 -0700408 'board': board,
409 'dimensions': self.dimensions,
410 'force': self.force,
411 'job_retry': self.job_retry,
412 'max_runtime_mins': _JOB_MAX_RUNTIME_MINS_DEFAULT,
413 'model': model,
414 'name': self.name,
415 'no_delay': self.no_delay,
416 'no_wait_for_results': not self.job_retry,
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700417 'num': self.num,
418 'pool': self.pool,
419 'priority': self.priority,
Jacob Kopczynskic1d0f5c2020-08-09 10:41:32 -0700420 'qs_account': self.qs_account,
421 'run_prod_code': run_prod_code,
422 'suite': self.suite,
423 'task_id': task_id,
424 'test_source_build': test_source_build,
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700425 'timeout': self.timeout,
426 'timeout_mins': _JOB_MAX_RUNTIME_MINS_DEFAULT,
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700427 }
428
Xinan Lin9e4917d2019-11-04 10:58:47 -0800429 task_executor.push(task_executor.SUITES_QUEUE,
430 tag=self.suite,
431 **suite_job_parameters)
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700432 logging.info('Pushing task %r into taskqueue', suite_job_parameters)
Xixuan Wu5451a662017-10-17 10:57:40 -0700433 self.is_pushed = True
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700434
Xinan Lin028f9582019-12-11 10:55:33 -0800435 def _schedule_cros_builds(self, build_dict, firmware_build_dict, configs):
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700436 """Schedule tasks with branch builds.
437
438 Args:
Craig Bergstrom58263d32018-04-26 14:11:35 -0600439 build_dict: the build dict for ChromeOS boards, see return
Xinan Linea1efcb2019-12-30 23:46:42 -0800440 value of |build_lib.get_cros_builds|.
Xinan Lin028f9582019-12-11 10:55:33 -0800441 firmware_build_dict: a dict of firmware artifact, see return value of
442 |base_event.get_firmware_builds|.
Xixuan Wuf4a4c882019-03-15 14:48:26 -0700443 configs: A config_reader.Configs object.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700444 """
Xixuan Wuf4a4c882019-03-15 14:48:26 -0700445 lab_config = configs.lab_config
C Shapiro7f24a002017-12-05 14:25:09 -0700446 models_by_board = lab_config.get_cros_model_map() if lab_config else {}
C Shapiro09108252019-08-01 14:52:52 -0500447 model_agnostic_cros_builds = set()
Xixuan Wu8d2f2862018-08-28 16:48:04 -0700448 for (board, passed_model, build_type,
Craig Bergstrom58263d32018-04-26 14:11:35 -0600449 milestone), manifest in build_dict.iteritems():
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700450 cros_build = str(build_lib.CrOSBuild(board, build_type, milestone,
451 manifest))
452 logging.info('Running %s on %s', self.name, cros_build)
Po-Hsien Wang6d589732018-05-15 17:19:34 -0700453 if self.exclude_boards and board in self.exclude_boards:
454 logging.debug('Board %s is in excluded board list: %s',
455 board, self.exclude_boards)
456 continue
Xixuan Wu8d2f2862018-08-28 16:48:04 -0700457
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700458 if self.boards and board not in self.boards:
459 logging.debug('Board %s is not in supported board list: %s',
460 board, self.boards)
461 continue
462
463 # Check the fitness of the build's branch for task
464 branch_build_spec = _pick_branch(build_type, milestone)
465 if not self._fits_spec(branch_build_spec):
Xinan Lindf0698a2020-02-05 22:38:11 -0800466 msg = ("branch_build spec %s doesn't fit this task's "
467 "requirement: %s") % (branch_build_spec,
468 ",".join(self.branch_specs))
469 logging.debug(msg)
470 self.job_section.add_schedule_job(board, passed_model, msg=msg)
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700471 continue
472
Xinan Lindf0698a2020-02-05 22:38:11 -0800473 # Record this build as it matches both board and branch specs.
474 if self.only_hwtest_sanity_required:
475 self.job_section.add_matched_relax_build(
476 board, build_type, milestone, manifest)
477 else:
478 self.job_section.add_matched_build(
479 board, build_type, milestone, manifest)
480
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700481 firmware_rw_build = None
482 firmware_ro_build = None
483 if self.firmware_rw_build_spec or self.firmware_ro_build_spec:
484 firmware_rw_build = self._get_firmware_build(
Xinan Lin028f9582019-12-11 10:55:33 -0800485 self.firmware_rw_build_spec, board, firmware_build_dict, lab_config)
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700486 firmware_ro_build = self._get_firmware_build(
Xinan Lin028f9582019-12-11 10:55:33 -0800487 self.firmware_ro_build_spec, board, firmware_build_dict, lab_config)
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700488
489 if not firmware_ro_build and self.firmware_ro_build_spec:
Xinan Lindf0698a2020-02-05 22:38:11 -0800490 msg = 'No RO firmware ro build to run, skip running'
491 logging.debug(msg)
492 self.job_section.add_schedule_job(board, passed_model, msg=msg)
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700493 continue
494
495 if self.test_source == build_lib.BuildType.FIRMWARE_RW:
496 test_source_build = firmware_rw_build
497 else:
Jacob Kopczynski79d00102018-07-13 15:37:03 -0700498 # Default test source build to CrOS build if it's not specified.
499 # Past versions chose based on run_prod_code, but we no longer respect
500 # that option and scheduler settings should always set it to False.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700501 test_source_build = cros_build
502
Xinan Lindf0698a2020-02-05 22:38:11 -0800503 # Record the matched firwmare build.
504 if firmware_rw_build:
505 self.job_section.add_matched_fw_build(
506 board,
507 self.firmware_rw_build_spec,
508 firmware_rw_build,
509 read_only=False)
510 if firmware_ro_build:
511 self.job_section.add_matched_fw_build(
512 board,
513 self.firmware_ro_build_spec,
514 firmware_ro_build,
515 read_only=True)
516
Xinan Lin6668e0f2020-05-29 10:02:57 -0700517 # Board above is used as build target to control the CrOS image.
518 # The following part is to assign models for lab boards, where
519 # the suffix should be removed.
Prathmesh Prabhu074b8d42020-08-13 17:17:33 +0000520 hwtest_board = build_lib.reshape_board(board)
521
Xinan Lin6668e0f2020-05-29 10:02:57 -0700522 models = models_by_board.get(hwtest_board, [None])
C Shapiro7f24a002017-12-05 14:25:09 -0700523
Harpreet Grewalbbbb7de2019-02-05 19:35:03 +0000524 for model in models:
525 if ((passed_model is not None and model == passed_model) or
526 passed_model is None):
Xinan Lin6668e0f2020-05-29 10:02:57 -0700527 full_model_name = '%s_%s' % (hwtest_board, model)
Xixuan Wua41efa22019-05-17 14:28:04 -0700528 # Respect exclude first.
529 if self.exclude_models and full_model_name in self.exclude_models:
530 logging.debug("Skip model %s as it's in exclude model list %s",
531 model, self.exclude_models)
532 continue
533
534 if self.models and full_model_name not in self.models:
535 logging.debug("Skip model %s as it's not in support model list %s",
536 model, self.models)
537 continue
538
C Shapiro09108252019-08-01 14:52:52 -0500539 explicit_model = model
540
541 if self.any_model:
Xinan Lin3ba18a02019-08-13 15:44:55 -0700542 explicit_model = None
C Shapiro09108252019-08-01 14:52:52 -0500543 unique_build = str(cros_build)
544 if unique_build in model_agnostic_cros_builds:
545 # Skip since we've already run with no explicit model set.
Xinan Lindf0698a2020-02-05 22:38:11 -0800546 msg = "Skip model %s as any_model enabled for this job." % model
547 self.job_section.add_schedule_job(board, model, msg=msg)
C Shapiro09108252019-08-01 14:52:52 -0500548 continue
549 model_agnostic_cros_builds.add(unique_build)
550
Xinan Lindf0698a2020-02-05 22:38:11 -0800551 task_id = str(uuid.uuid1())
Harpreet Grewalbbbb7de2019-02-05 19:35:03 +0000552 self._push_suite(
Xinan Lindf0698a2020-02-05 22:38:11 -0800553 task_id=task_id,
Xixuan Wub4b2f412019-05-03 11:22:31 -0700554 board=hwtest_board,
C Shapiro09108252019-08-01 14:52:52 -0500555 model=explicit_model,
Harpreet Grewalbbbb7de2019-02-05 19:35:03 +0000556 cros_build=cros_build,
557 firmware_rw_build=firmware_rw_build,
558 firmware_ro_build=firmware_ro_build,
Xinan Lin0550f492020-01-21 16:25:53 -0800559 test_source_build=test_source_build)
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700560
Xinan Lin6668e0f2020-05-29 10:02:57 -0700561 # Analytics table stores the build target instead of the lab board.
Xinan Lin0673a182020-04-14 15:09:35 -0700562 self.job_section.add_schedule_job(
563 board, explicit_model, task_id=task_id)
Xinan Lindf0698a2020-02-05 22:38:11 -0800564
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700565 def _schedule_launch_control_builds(self, launch_control_builds):
566 """Schedule tasks with launch control builds.
567
568 Args:
569 launch_control_builds: the build dict for Android boards.
570 """
571 for board, launch_control_build in launch_control_builds.iteritems():
572 logging.debug('Running %s on %s', self.name, board)
Po-Hsien Wang6d589732018-05-15 17:19:34 -0700573 if self.exclude_boards and board in self.exclude_boards:
574 logging.debug('Board %s is in excluded board list: %s',
575 board, self.exclude_boards)
576 continue
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700577 if self.boards and board not in self.boards:
578 logging.debug('Board %s is not in supported board list: %s',
579 board, self.boards)
580 continue
581
582 for android_build in launch_control_build:
583 if not any([branch in android_build
584 for branch in self.launch_control_branches]):
585 logging.debug('Branch %s is not required to run for task '
586 '%s', android_build, self.name)
587 continue
588
589 self._push_suite(board=board,
590 test_source_build=android_build,
591 launch_control_build=android_build)
592
593
594def _pick_branch(build_type, milestone):
595 """Select branch based on build type.
596
597 If the build_type is a bare branch, return build_type as the build spec.
598 If the build_type is a normal CrOS branch, return milestone as the build
599 spec.
600
601 Args:
602 build_type: a string builder name, like 'release'.
603 milestone: a string milestone, like '55'.
604
605 Returns:
606 A string milestone if build_type represents CrOS build, otherwise
607 return build_type.
608 """
609 return build_type if build_type in build_lib.BARE_BRANCHES else milestone
Xinan Linae7d6372019-09-12 14:42:10 -0700610
611
612def _split_unibuilds(build_dict, configs):
613 """Split the uni-builds to all models under a board.
614
615 Args:
616 build_dict: the build dict for ChromeOS boards, see return
Xinan Linea1efcb2019-12-30 23:46:42 -0800617 value of |build_lib.get_cros_builds|.
Xinan Linae7d6372019-09-12 14:42:10 -0700618 configs: a config_reader.Configs object.
619
620 Returns:
621 A build dict.
622 """
623 models_by_board = configs.lab_config.get_cros_model_map()
624 if not models_by_board:
625 return build_dict
626 all_branch_build_dict = {}
627 for (board, model, config, milestone), platform in build_dict.iteritems():
628 uni_build_models = models_by_board.get(board)
629 if uni_build_models is not None and model is None:
630 for uni_build_model in uni_build_models:
631 model_key = (board, uni_build_model, config, milestone)
632 _add_build_dict(all_branch_build_dict, model_key, platform)
633 continue
634 build_key = (board, model, config, milestone)
635 _add_build_dict(all_branch_build_dict, build_key, platform)
636
637 return all_branch_build_dict
638
639
640def _add_build_dict(build_dict, key, value):
641 """A wrapper to add or update an item in build_dict."""
642 cur_manifest = build_dict.get(key)
643 if cur_manifest is None:
644 build_dict[key] = value
645 return
646 build_dict[key] = max(
647 [cur_manifest, value], key=version.LooseVersion)