blob: 1a816454d13c1e65cbc116d6997380f600d583fc [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.
Taylor Clark54427ce2021-02-18 21:59:27 +000048 analytics_name, the build rule name. Initially build rule or test rule
49 name was used in suite scheduler's dashboard for analytics. Later it
50 was expanded to the entire pipeline and we want to tag all requests
51 from Suite Scheduler with the rule name.
Po-Hsien Wangdd833072018-08-16 18:09:20 -070052 board_families, a common separated list of board family to run this
53 task on. Boards belong to one of the board family in this list
54 would be added to task_info.boards.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -070055 boards, a comma separated list of boards to run this task on. Default
Po-Hsien Wangdd833072018-08-16 18:09:20 -070056 is None, which allows this task to run on all boards. If same board
57 is specified in 'boards' and 'exclude_boards', we exclude this
58 board.
Xinan Linc8647112020-02-04 16:45:56 -080059 dimensions, a comma separated lists of labels. Each label is in
60 the form of 'key:value'.
Po-Hsien Wangdd833072018-08-16 18:09:20 -070061 exclude_board_families, a common separated list of board family not to
62 run task on. Boards belong to one of the board family in this list
63 would be added to task_info.exclude_boards.
Po-Hsien Wang6d589732018-05-15 17:19:34 -070064 exclude_boards, a comma separated list of boards not to run this task
65 on. Default is None, which allows this task to run on all boards.
Po-Hsien Wangdd833072018-08-16 18:09:20 -070066 If same board is specified in 'boards' and 'exclude_boards', we
67 exclude this board.
Xixuan Wu89897182019-01-03 15:28:01 -080068 models, a comma separated list of models to run this task on. Default
69 is None, which allows this task to run on all models. If same model
70 is specified in 'models' and 'exclude_models', we exclude this
71 model.
72 exclude_models, a comma separated list of models not to run this task
73 on. Default is None, which allows this task to run on all models.
C Shapiro09108252019-08-01 14:52:52 -050074 any_model, set to True to not pass the model parameter and allow
75 a test suite to run any/all models available for testing.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -070076 priority, the string name of a priority from constants.Priorities.
77 timeout, the max lifetime of the suite in hours.
78 cros_build_spec, spec used to determine the ChromeOS build to test
79 with a firmware build, e.g., tot, R41 etc.
80 firmware_rw_build_spec, spec used to determine the firmware RW build
81 test with a ChromeOS build.
82 firmware_ro_build_spec, spec used to determine the firmware RO build
83 test with a ChromeOS build.
Brigit Rossbachbb080912020-11-18 13:52:17 -070084 firmware_ro_version, pinned firmware RO version.
85 firmware_rw_version, pinned firmware RW version.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -070086 test_source, the source of test code when firmware will be updated in
87 the test. The value can be 'firmware_rw', 'firmware_ro' or 'cros'.
88 job_retry, set to True to enable job-level retry. Default is False.
Xixuan Wu80531932017-10-12 17:26:51 -070089 no_delay, set to True to raise the priority of this task in task.
90 force, set to True to schedule this suite no matter whether there's
91 duplicate jobs before.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -070092 queue, so the suite jobs can start running tests with no waiting.
93 hour, an integer specifying the hour that a nightly run should be
94 triggered, default is set to 21.
95 day, an integer specifying the day of a week that a weekly run should
96 be triggered, default is set to 5 (Saturday).
97 os_type, type of OS, e.g., cros, brillo, android. Default is cros.
98 The argument is required for android/brillo builds.
99 launch_control_branches, comma separated string of launch control
100 branches. The argument is required and only applicable for
101 android/brillo builds.
102 launch_control_targets, comma separated string of build targets for
103 launch control builds. The argument is required and only
104 applicable for android/brillo builds.
105 testbed_dut_count, number of duts to test when using a testbed.
Xinan Lin4757d6f2020-03-24 22:20:31 -0700106 qs_account, quota account for the unmanaged pool which has enabled
107 Quota Scheduler.
Jacob Kopczynskic54520f2020-08-07 13:20:12 -0700108
Xixuan Wu83118dd2018-08-27 12:11:35 -0700109 board_family_config: A board family dictionary mapping board_family name
110 to its corresponding boards.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700111 tot: The tot manager for checking ToT. If it's None, a new tot_manager
112 instance will be initialized.
Xinan Lin33937d62020-04-14 14:41:23 -0700113 is_sanity: A boolean; true if we are running in sanity env.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700114 """
Xixuan Wu5451a662017-10-17 10:57:40 -0700115 # Indicate whether there're suites get pushed into taskqueue for this task.
116 self.is_pushed = False
117
Taylor Clark8b06a832021-02-16 21:45:42 +0000118 self.analytics_name = task_info.analytics_name
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700119 self.branch_specs = task_info.branch_specs
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700120 self.cros_build_spec = task_info.cros_build_spec
Xixuan Wu008ee832017-10-12 16:59:34 -0700121 self.day = task_info.day
Jacob Kopczynskic1d0f5c2020-08-09 10:41:32 -0700122 self.firmware_ro_build_spec = task_info.firmware_ro_build_spec
123 self.firmware_rw_build_spec = task_info.firmware_rw_build_spec
Brigit Rossbachbb080912020-11-18 13:52:17 -0700124 self.firmware_ro_version = task_info.firmware_ro_version
125 self.firmware_rw_version = task_info.firmware_rw_version
Jacob Kopczynskic1d0f5c2020-08-09 10:41:32 -0700126 self.force = task_info.force
127 self.frontdoor = task_info.frontdoor
128 self.hour = task_info.hour
129 self.job_retry = task_info.job_retry
130 self.name = task_info.name
131 self.no_delay = task_info.no_delay
132 self.num = task_info.num
Craig Bergstrom58263d32018-04-26 14:11:35 -0600133 self.only_hwtest_sanity_required = task_info.only_hwtest_sanity_required
Jacob Kopczynskic1d0f5c2020-08-09 10:41:32 -0700134 self.os_type = task_info.os_type
135 self.pool = task_info.pool
136 self.priority = task_info.priority
Xinan Lin4757d6f2020-03-24 22:20:31 -0700137 self.qs_account = task_info.qs_account
Jacob Kopczynskic1d0f5c2020-08-09 10:41:32 -0700138 self.suite = task_info.suite
139 self.test_source = task_info.test_source
140 self.testbed_dut_count = task_info.testbed_dut_count
141 self.timeout = task_info.timeout
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700142
143 if task_info.lc_branches:
144 self.launch_control_branches = [
145 t.strip() for t in task_info.lc_branches.split(',')]
146 else:
147 self.launch_control_branches = []
148
149 if task_info.lc_targets:
150 self.launch_control_targets = [
151 t.strip() for t in task_info.lc_targets.split(',')]
152 else:
153 self.launch_control_targets = []
154
155 if task_info.boards:
156 self.boards = [t.strip() for t in task_info.boards.split(',')]
157 else:
158 self.boards = []
159
Po-Hsien Wang6d589732018-05-15 17:19:34 -0700160 if task_info.exclude_boards:
161 self.exclude_boards = [
162 t.strip() for t in task_info.exclude_boards.split(',')]
163 else:
164 self.exclude_boards = []
165
Xixuan Wu89897182019-01-03 15:28:01 -0800166 if task_info.models:
167 self.models = [t.strip() for t in task_info.models.split(',')]
168 else:
169 self.models = []
170
171 if task_info.exclude_models:
172 self.exclude_models = [
173 t.strip() for t in task_info.exclude_models.split(',')]
174 else:
175 self.exclude_models = []
176
C Shapiro09108252019-08-01 14:52:52 -0500177 self.any_model = task_info.any_model
178
Po-Hsien Wangdd833072018-08-16 18:09:20 -0700179 if task_info.board_families:
180 # Finetune the allowed boards list with board_families & boards.
181 families = [family.strip()
182 for family in task_info.board_families.split(',')]
183 for family in families:
184 self.boards += board_family_config.get(family, [])
Xixuan Wu89897182019-01-03 15:28:01 -0800185
Po-Hsien Wangdd833072018-08-16 18:09:20 -0700186 if task_info.exclude_board_families:
187 # Finetune the disallowed boards list with exclude_board_families
188 # & exclude_boards.
189 families = [family.strip()
190 for family in task_info.exclude_board_families.split(',')]
191 for family in families:
192 self.exclude_boards += board_family_config.get(family, [])
193
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700194 if tot is None:
195 self.tot_manager = tot_manager.TotMilestoneManager()
196 else:
197 self.tot_manager = tot
198
Xinan Linc8647112020-02-04 16:45:56 -0800199 self.dimensions = task_info.dimensions
200
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700201 self._set_spec_compare_info()
Xinan Lin33937d62020-04-14 14:41:23 -0700202 # Sanity test does not have to upload metrics.
203 if not is_sanity:
204 self.job_section = analytics.ScheduleJobSection(task_info)
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700205
Xinan Lin028f9582019-12-11 10:55:33 -0800206 def schedule(self, launch_control_builds, cros_builds_tuple,
207 firmware_builds, configs):
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700208 """Schedule the task by its settings.
209
210 Args:
211 launch_control_builds: the build dict for Android boards, see
212 return value of |get_launch_control_builds|.
Craig Bergstrom58263d32018-04-26 14:11:35 -0600213 cros_builds_tuple: the two-tuple of build dicts for ChromeOS boards,
214 see return value of |get_cros_builds|.
Xinan Lin028f9582019-12-11 10:55:33 -0800215 firmware_builds: a dict of firmware artifact, see return value of
216 |base_event.get_firmware_builds|.
Xixuan Wuf4a4c882019-03-15 14:48:26 -0700217 configs: a config_reader.Configs object.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700218
219 Raises:
220 SchedulingError: if tasks that should be scheduled fail to schedule.
Xixuan Wu5451a662017-10-17 10:57:40 -0700221
222 Returns:
Jacob Kopczynski79d00102018-07-13 15:37:03 -0700223 A boolean indicator; true if there were any suites related to this
224 task which got pushed into the suites queue.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700225 """
Xixuan Wuf4a4c882019-03-15 14:48:26 -0700226 assert configs.lab_config is not None
Xixuan Wu5451a662017-10-17 10:57:40 -0700227 self.is_pushed = False
228
Craig Bergstrom58263d32018-04-26 14:11:35 -0600229 branch_builds, relaxed_builds = cros_builds_tuple
230 builds_dict = branch_builds
231 if self.only_hwtest_sanity_required:
Xinan Linae7d6372019-09-12 14:42:10 -0700232 builds_dict = _split_unibuilds(relaxed_builds, configs)
Craig Bergstrom58263d32018-04-26 14:11:35 -0600233
Xinan Lindf0698a2020-02-05 22:38:11 -0800234 # Record all target boards and models into job section.
235 lab_config = configs.lab_config
236 models_by_board = lab_config.get_cros_model_map() if lab_config else {}
237 boards = self.boards if self.boards else lab_config.get_cros_board_list()
238 for b in boards:
239 if self.exclude_boards and b in self.exclude_boards:
240 continue
241 self.job_section.add_board(b)
242 models = self.models or models_by_board.get(b, [])
243 for m in models:
244 if m and '%s_%s' % (b, m) not in self.exclude_models:
245 self.job_section.add_model(m)
246
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700247 logging.info('######## Scheduling task %s ########', self.name)
248 if self.os_type == build_lib.OS_TYPE_CROS:
Craig Bergstrom58263d32018-04-26 14:11:35 -0600249 if not builds_dict:
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700250 logging.info('No CrOS build to run, skip running.')
251 else:
Xinan Lin028f9582019-12-11 10:55:33 -0800252 self._schedule_cros_builds(builds_dict, firmware_builds, configs)
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700253 else:
254 if not launch_control_builds:
255 logging.info('No Android build to run, skip running.')
256 else:
257 self._schedule_launch_control_builds(launch_control_builds)
Xinan Lindf0698a2020-02-05 22:38:11 -0800258 upload_result = False
259 try:
260 upload_result = self.job_section.upload()
261 # For any exceptions from BQ, only log it and move on.
262 except Exception as e: #pylint: disable=broad-except
263 logging.exception(str(e))
264 if not upload_result:
265 logging.warning('Failed to insert row: %r', self.job_section)
Xixuan Wu5451a662017-10-17 10:57:40 -0700266 return self.is_pushed
267
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700268 def _set_spec_compare_info(self):
269 """Set branch spec compare info for task for further check."""
270 self._bare_branches = []
271 self._version_equal_constraint = False
272 self._version_gte_constraint = False
273 self._version_lte_constraint = False
274
275 if not self.branch_specs:
276 # Any milestone is OK.
277 self._numeric_constraint = version.LooseVersion('0')
278 else:
279 self._numeric_constraint = None
280 for spec in self.branch_specs:
281 if 'tot' in spec.lower():
282 # Convert spec >=tot-1 to >=RXX.
283 tot_str = spec[spec.index('tot'):]
284 spec = spec.replace(
285 tot_str, self.tot_manager.convert_tot_spec(tot_str))
286
287 if spec.startswith('>='):
288 self._numeric_constraint = version.LooseVersion(
289 spec.lstrip('>=R'))
290 self._version_gte_constraint = True
291 elif spec.startswith('<='):
292 self._numeric_constraint = version.LooseVersion(
293 spec.lstrip('<=R'))
294 self._version_lte_constraint = True
295 elif spec.startswith('=='):
296 self._version_equal_constraint = True
297 self._numeric_constraint = version.LooseVersion(
298 spec.lstrip('==R'))
299 else:
300 self._bare_branches.append(spec)
301
302 def _fits_spec(self, branch):
303 """Check if a branch is deemed OK by this task's branch specs.
304
305 Will return whether a branch 'fits' the specifications stored in this task.
306
307 Examples:
308 Assuming tot=R40
309 t = Task('Name', 'suite', ['factory', '>=tot-1'])
310 t._fits_spec('factory') # True
311 t._fits_spec('40') # True
312 t._fits_spec('38') # False
313 t._fits_spec('firmware') # False
314
315 Args:
316 branch: the branch to check.
317
318 Returns:
319 True if branch 'fits' with stored specs, False otherwise.
320 """
321 if branch in build_lib.BARE_BRANCHES:
322 return branch in self._bare_branches
323
324 if self._numeric_constraint:
325 if self._version_equal_constraint:
326 return version.LooseVersion(branch) == self._numeric_constraint
327 elif self._version_gte_constraint:
328 return version.LooseVersion(branch) >= self._numeric_constraint
329 elif self._version_lte_constraint:
330 return version.LooseVersion(branch) <= self._numeric_constraint
331 else:
332 return version.LooseVersion(branch) >= self._numeric_constraint
333 else:
334 return False
335
Xinan Lin028f9582019-12-11 10:55:33 -0800336 def _get_firmware_build(self, spec, board, firmware_build_dict, lab_config):
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700337 """Get the firmware build name to test with ChromeOS build.
338
339 Args:
340 spec: a string build spec for RO or RW firmware, eg. firmware,
341 cros. For RO firmware, the value can also be released_ro_X.
342 board: a string board against which this task will run suite job.
Xinan Lin028f9582019-12-11 10:55:33 -0800343 firmware_build_dict: a dict of firmware artifacts, see return value of
344 |base_event.get_firmware_builds|.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700345 lab_config: a config.LabConfig object, to read lab config file.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700346
347 Returns:
348 A string firmware build name.
349
350 Raises:
351 ValueError: if failing to get firmware from lab config file;
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700352 """
353 if not spec or spec == 'stable':
354 # TODO(crbug.com/577316): Query stable RO firmware.
355 logging.debug('%s RO firmware build is not supported.', spec)
356 return None
357
358 try:
Dhanya Ganeshf6014b72020-08-04 22:33:28 +0000359 if firmware_build_dict:
Xinan Lin028f9582019-12-11 10:55:33 -0800360 return firmware_build_dict.get((spec, board), None)
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700361 else:
Xinan Lin028f9582019-12-11 10:55:33 -0800362 return None
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700363 except ValueError as e:
364 logging.warning('Failed to get firmware from lab config file'
365 'for spec %s, board %s: %s', spec, board, str(e))
366 return None
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700367
C Shapiro7f24a002017-12-05 14:25:09 -0700368 def _push_suite(
369 self,
Xinan Lindf0698a2020-02-05 22:38:11 -0800370 task_id=None,
C Shapiro7f24a002017-12-05 14:25:09 -0700371 board=None,
372 model=None,
373 cros_build=None,
374 firmware_rw_build=None,
375 firmware_ro_build=None,
376 test_source_build=None,
377 launch_control_build=None,
Xinan Lin4757d6f2020-03-24 22:20:31 -0700378 run_prod_code=False):
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700379 """Schedule suite job for the task by pushing suites to SuiteQueue.
380
381 Args:
Xinan Lindf0698a2020-02-05 22:38:11 -0800382 task_id: the id to track this task in exectuion.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700383 board: the board against which this suite job run.
C Shapiro7f24a002017-12-05 14:25:09 -0700384 model: the model name for unibuild.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700385 cros_build: the CrOS build of this suite job.
386 firmware_rw_build: Firmware RW build to run this suite job with.
387 firmware_ro_build: Firmware RO build to run this suite job with.
388 test_source_build: Test source build, used for server-side
389 packaging of this suite job.
390 launch_control_build: the launch control build of this suite job.
391 run_prod_code: If True, the suite will run the test code that lives
392 in prod aka the test code currently on the lab servers. If
393 False, the control files and test code for this suite run will
394 be retrieved from the build artifacts. Default is False.
395 """
396 android_build = None
397 testbed_build = None
398
399 if self.testbed_dut_count:
400 launch_control_build = '%s#%d' % (launch_control_build,
401 self.testbed_dut_count)
402 test_source_build = launch_control_build
403 board = '%s-%d' % (board, self.testbed_dut_count)
404
405 if launch_control_build:
406 if not self.testbed_dut_count:
407 android_build = launch_control_build
408 else:
409 testbed_build = launch_control_build
410
411 suite_job_parameters = {
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700412 build_lib.BuildVersionKey.ANDROID_BUILD_VERSION: android_build,
Jacob Kopczynskic1d0f5c2020-08-09 10:41:32 -0700413 build_lib.BuildVersionKey.CROS_VERSION: cros_build,
414 build_lib.BuildVersionKey.FW_RO_VERSION: firmware_ro_build,
415 build_lib.BuildVersionKey.FW_RW_VERSION: firmware_rw_build,
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700416 build_lib.BuildVersionKey.TESTBED_BUILD_VERSION: testbed_build,
Taylor Clark8b06a832021-02-16 21:45:42 +0000417 'analytics_name': self.analytics_name,
Jacob Kopczynskic1d0f5c2020-08-09 10:41:32 -0700418 'board': board,
419 'dimensions': self.dimensions,
420 'force': self.force,
421 'job_retry': self.job_retry,
422 'max_runtime_mins': _JOB_MAX_RUNTIME_MINS_DEFAULT,
423 'model': model,
424 'name': self.name,
425 'no_delay': self.no_delay,
426 'no_wait_for_results': not self.job_retry,
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700427 'num': self.num,
428 'pool': self.pool,
429 'priority': self.priority,
Jacob Kopczynskic1d0f5c2020-08-09 10:41:32 -0700430 'qs_account': self.qs_account,
431 'run_prod_code': run_prod_code,
432 'suite': self.suite,
433 'task_id': task_id,
434 'test_source_build': test_source_build,
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700435 'timeout': self.timeout,
436 'timeout_mins': _JOB_MAX_RUNTIME_MINS_DEFAULT,
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700437 }
438
Xinan Lin9e4917d2019-11-04 10:58:47 -0800439 task_executor.push(task_executor.SUITES_QUEUE,
440 tag=self.suite,
441 **suite_job_parameters)
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700442 logging.info('Pushing task %r into taskqueue', suite_job_parameters)
Xixuan Wu5451a662017-10-17 10:57:40 -0700443 self.is_pushed = True
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700444
Xinan Lin028f9582019-12-11 10:55:33 -0800445 def _schedule_cros_builds(self, build_dict, firmware_build_dict, configs):
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700446 """Schedule tasks with branch builds.
447
448 Args:
Craig Bergstrom58263d32018-04-26 14:11:35 -0600449 build_dict: the build dict for ChromeOS boards, see return
Xinan Linea1efcb2019-12-30 23:46:42 -0800450 value of |build_lib.get_cros_builds|.
Xinan Lin028f9582019-12-11 10:55:33 -0800451 firmware_build_dict: a dict of firmware artifact, see return value of
452 |base_event.get_firmware_builds|.
Xixuan Wuf4a4c882019-03-15 14:48:26 -0700453 configs: A config_reader.Configs object.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700454 """
Xixuan Wuf4a4c882019-03-15 14:48:26 -0700455 lab_config = configs.lab_config
C Shapiro7f24a002017-12-05 14:25:09 -0700456 models_by_board = lab_config.get_cros_model_map() if lab_config else {}
C Shapiro09108252019-08-01 14:52:52 -0500457 model_agnostic_cros_builds = set()
Xixuan Wu8d2f2862018-08-28 16:48:04 -0700458 for (board, passed_model, build_type,
Craig Bergstrom58263d32018-04-26 14:11:35 -0600459 milestone), manifest in build_dict.iteritems():
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700460 cros_build = str(build_lib.CrOSBuild(board, build_type, milestone,
461 manifest))
462 logging.info('Running %s on %s', self.name, cros_build)
Po-Hsien Wang6d589732018-05-15 17:19:34 -0700463 if self.exclude_boards and board in self.exclude_boards:
464 logging.debug('Board %s is in excluded board list: %s',
465 board, self.exclude_boards)
466 continue
Xixuan Wu8d2f2862018-08-28 16:48:04 -0700467
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700468 if self.boards and board not in self.boards:
469 logging.debug('Board %s is not in supported board list: %s',
470 board, self.boards)
471 continue
472
473 # Check the fitness of the build's branch for task
474 branch_build_spec = _pick_branch(build_type, milestone)
475 if not self._fits_spec(branch_build_spec):
Xinan Lindf0698a2020-02-05 22:38:11 -0800476 msg = ("branch_build spec %s doesn't fit this task's "
477 "requirement: %s") % (branch_build_spec,
478 ",".join(self.branch_specs))
479 logging.debug(msg)
480 self.job_section.add_schedule_job(board, passed_model, msg=msg)
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700481 continue
482
Xinan Lindf0698a2020-02-05 22:38:11 -0800483 # Record this build as it matches both board and branch specs.
484 if self.only_hwtest_sanity_required:
485 self.job_section.add_matched_relax_build(
486 board, build_type, milestone, manifest)
487 else:
488 self.job_section.add_matched_build(
489 board, build_type, milestone, manifest)
490
Sean McAllister7378b692021-06-03 15:44:22 -0600491 # check that we only got one spec for ro firmware
492 if all([self.firmware_ro_version, self.firmware_ro_build_spec]):
493 logging.error(
494 "Exactly one of firmware_ro_version or firmware_ro_build_spec " \
495 "allowed, got: %s and %s" % (
496 self.firmware_ro_version,
497 self.firmware_ro_build_spec,
498 ),
499 )
500 continue
501
502 # check that we only got one spec for rw firmware
503 if all([self.firmware_rw_version, self.firmware_rw_build_spec]):
504 logging.error(
505 "Exactly one of firmware_rw_version or firmware_rw_build_spec " \
506 "allowed, got: %s and %s" % (
507 self.firmware_rw_version,
508 self.firmware_rw_build_spec,
509 ),
510 )
511 continue
512
513 # resolve ro firmware version to provision
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700514 firmware_ro_build = None
Brigit Rossbachbb080912020-11-18 13:52:17 -0700515 if self.firmware_ro_version:
516 firmware_ro_build = self.firmware_ro_version
Sean McAllister7378b692021-06-03 15:44:22 -0600517 elif self.firmware_ro_build_spec:
518 firmware_ro_build = self._get_firmware_build(
519 self.firmware_ro_build_spec, board, firmware_build_dict, lab_config)
Brigit Rossbachbb080912020-11-18 13:52:17 -0700520
Sean McAllister7378b692021-06-03 15:44:22 -0600521 if not firmware_ro_build:
522 msg = 'No RO firmware build to run, skip running'
523 logging.debug(msg)
524 self.job_section.add_schedule_job(board, passed_model, msg=msg)
525 continue
526
527 # resolve rw firmware version to provision
528 firmware_rw_build = None
Brigit Rossbachbb080912020-11-18 13:52:17 -0700529 if self.firmware_rw_version:
530 firmware_rw_build = self.firmware_rw_version
Sean McAllister7378b692021-06-03 15:44:22 -0600531 elif self.firmware_rw_build_spec:
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700532 firmware_rw_build = self._get_firmware_build(
Sean McAllister7378b692021-06-03 15:44:22 -0600533 self.firmware_rw_build_spec, board, firmware_build_dict, lab_config)
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700534
Sean McAllister7378b692021-06-03 15:44:22 -0600535 if not firmware_rw_build:
536 msg = 'No RW firmware build to run, skip running'
537 logging.debug(msg)
538 self.job_section.add_schedule_job(board, passed_model, msg=msg)
539 continue
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700540
541 if self.test_source == build_lib.BuildType.FIRMWARE_RW:
542 test_source_build = firmware_rw_build
543 else:
Jacob Kopczynski79d00102018-07-13 15:37:03 -0700544 # Default test source build to CrOS build if it's not specified.
545 # Past versions chose based on run_prod_code, but we no longer respect
546 # that option and scheduler settings should always set it to False.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700547 test_source_build = cros_build
548
Xinan Lindf0698a2020-02-05 22:38:11 -0800549 # Record the matched firwmare build.
550 if firmware_rw_build:
551 self.job_section.add_matched_fw_build(
552 board,
553 self.firmware_rw_build_spec,
554 firmware_rw_build,
555 read_only=False)
556 if firmware_ro_build:
557 self.job_section.add_matched_fw_build(
558 board,
559 self.firmware_ro_build_spec,
560 firmware_ro_build,
561 read_only=True)
562
Xinan Lin6668e0f2020-05-29 10:02:57 -0700563 # Board above is used as build target to control the CrOS image.
564 # The following part is to assign models for lab boards, where
565 # the suffix should be removed.
Prathmesh Prabhu074b8d42020-08-13 17:17:33 +0000566 hwtest_board = build_lib.reshape_board(board)
567
Xinan Lin6668e0f2020-05-29 10:02:57 -0700568 models = models_by_board.get(hwtest_board, [None])
C Shapiro7f24a002017-12-05 14:25:09 -0700569
Harpreet Grewalbbbb7de2019-02-05 19:35:03 +0000570 for model in models:
571 if ((passed_model is not None and model == passed_model) or
572 passed_model is None):
Xinan Lin6668e0f2020-05-29 10:02:57 -0700573 full_model_name = '%s_%s' % (hwtest_board, model)
Xixuan Wua41efa22019-05-17 14:28:04 -0700574 # Respect exclude first.
575 if self.exclude_models and full_model_name in self.exclude_models:
576 logging.debug("Skip model %s as it's in exclude model list %s",
577 model, self.exclude_models)
578 continue
579
580 if self.models and full_model_name not in self.models:
581 logging.debug("Skip model %s as it's not in support model list %s",
582 model, self.models)
583 continue
584
C Shapiro09108252019-08-01 14:52:52 -0500585 explicit_model = model
586
587 if self.any_model:
Xinan Lin3ba18a02019-08-13 15:44:55 -0700588 explicit_model = None
C Shapiro09108252019-08-01 14:52:52 -0500589 unique_build = str(cros_build)
590 if unique_build in model_agnostic_cros_builds:
591 # Skip since we've already run with no explicit model set.
Xinan Lindf0698a2020-02-05 22:38:11 -0800592 msg = "Skip model %s as any_model enabled for this job." % model
593 self.job_section.add_schedule_job(board, model, msg=msg)
C Shapiro09108252019-08-01 14:52:52 -0500594 continue
595 model_agnostic_cros_builds.add(unique_build)
596
Xinan Lindf0698a2020-02-05 22:38:11 -0800597 task_id = str(uuid.uuid1())
Harpreet Grewalbbbb7de2019-02-05 19:35:03 +0000598 self._push_suite(
Xinan Lindf0698a2020-02-05 22:38:11 -0800599 task_id=task_id,
Xixuan Wub4b2f412019-05-03 11:22:31 -0700600 board=hwtest_board,
C Shapiro09108252019-08-01 14:52:52 -0500601 model=explicit_model,
Harpreet Grewalbbbb7de2019-02-05 19:35:03 +0000602 cros_build=cros_build,
603 firmware_rw_build=firmware_rw_build,
604 firmware_ro_build=firmware_ro_build,
Xinan Lin0550f492020-01-21 16:25:53 -0800605 test_source_build=test_source_build)
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700606
Xinan Lin6668e0f2020-05-29 10:02:57 -0700607 # Analytics table stores the build target instead of the lab board.
Xinan Lin0673a182020-04-14 15:09:35 -0700608 self.job_section.add_schedule_job(
609 board, explicit_model, task_id=task_id)
Xinan Lindf0698a2020-02-05 22:38:11 -0800610
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700611 def _schedule_launch_control_builds(self, launch_control_builds):
612 """Schedule tasks with launch control builds.
613
614 Args:
615 launch_control_builds: the build dict for Android boards.
616 """
617 for board, launch_control_build in launch_control_builds.iteritems():
618 logging.debug('Running %s on %s', self.name, board)
Po-Hsien Wang6d589732018-05-15 17:19:34 -0700619 if self.exclude_boards and board in self.exclude_boards:
620 logging.debug('Board %s is in excluded board list: %s',
621 board, self.exclude_boards)
622 continue
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700623 if self.boards and board not in self.boards:
624 logging.debug('Board %s is not in supported board list: %s',
625 board, self.boards)
626 continue
627
628 for android_build in launch_control_build:
629 if not any([branch in android_build
630 for branch in self.launch_control_branches]):
631 logging.debug('Branch %s is not required to run for task '
632 '%s', android_build, self.name)
633 continue
634
635 self._push_suite(board=board,
636 test_source_build=android_build,
637 launch_control_build=android_build)
638
639
640def _pick_branch(build_type, milestone):
641 """Select branch based on build type.
642
643 If the build_type is a bare branch, return build_type as the build spec.
644 If the build_type is a normal CrOS branch, return milestone as the build
645 spec.
646
647 Args:
648 build_type: a string builder name, like 'release'.
649 milestone: a string milestone, like '55'.
650
651 Returns:
652 A string milestone if build_type represents CrOS build, otherwise
653 return build_type.
654 """
655 return build_type if build_type in build_lib.BARE_BRANCHES else milestone
Xinan Linae7d6372019-09-12 14:42:10 -0700656
657
658def _split_unibuilds(build_dict, configs):
659 """Split the uni-builds to all models under a board.
660
661 Args:
662 build_dict: the build dict for ChromeOS boards, see return
Xinan Linea1efcb2019-12-30 23:46:42 -0800663 value of |build_lib.get_cros_builds|.
Xinan Linae7d6372019-09-12 14:42:10 -0700664 configs: a config_reader.Configs object.
665
666 Returns:
667 A build dict.
668 """
669 models_by_board = configs.lab_config.get_cros_model_map()
670 if not models_by_board:
671 return build_dict
672 all_branch_build_dict = {}
673 for (board, model, config, milestone), platform in build_dict.iteritems():
674 uni_build_models = models_by_board.get(board)
675 if uni_build_models is not None and model is None:
676 for uni_build_model in uni_build_models:
677 model_key = (board, uni_build_model, config, milestone)
678 _add_build_dict(all_branch_build_dict, model_key, platform)
679 continue
680 build_key = (board, model, config, milestone)
681 _add_build_dict(all_branch_build_dict, build_key, platform)
682
683 return all_branch_build_dict
684
685
686def _add_build_dict(build_dict, key, value):
687 """A wrapper to add or update an item in build_dict."""
688 cur_manifest = build_dict.get(key)
689 if cur_manifest is None:
690 build_dict[key] = value
691 return
692 build_dict[key] = max(
693 [cur_manifest, value], key=version.LooseVersion)