blob: fc6257c3c047373fc7d727ab95f74862b340f396 [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
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700491 firmware_rw_build = None
492 firmware_ro_build = None
Brigit Rossbachbb080912020-11-18 13:52:17 -0700493 if self.firmware_ro_version:
494 firmware_ro_build = self.firmware_ro_version
495
496 if self.firmware_rw_version:
497 firmware_rw_build = self.firmware_rw_version
498
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700499 if self.firmware_rw_build_spec or self.firmware_ro_build_spec:
500 firmware_rw_build = self._get_firmware_build(
Xinan Lin028f9582019-12-11 10:55:33 -0800501 self.firmware_rw_build_spec, board, firmware_build_dict, lab_config)
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700502 firmware_ro_build = self._get_firmware_build(
Xinan Lin028f9582019-12-11 10:55:33 -0800503 self.firmware_ro_build_spec, board, firmware_build_dict, lab_config)
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700504
505 if not firmware_ro_build and self.firmware_ro_build_spec:
Xinan Lindf0698a2020-02-05 22:38:11 -0800506 msg = 'No RO firmware ro build to run, skip running'
507 logging.debug(msg)
508 self.job_section.add_schedule_job(board, passed_model, msg=msg)
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700509 continue
510
511 if self.test_source == build_lib.BuildType.FIRMWARE_RW:
512 test_source_build = firmware_rw_build
513 else:
Jacob Kopczynski79d00102018-07-13 15:37:03 -0700514 # Default test source build to CrOS build if it's not specified.
515 # Past versions chose based on run_prod_code, but we no longer respect
516 # that option and scheduler settings should always set it to False.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700517 test_source_build = cros_build
518
Xinan Lindf0698a2020-02-05 22:38:11 -0800519 # Record the matched firwmare build.
520 if firmware_rw_build:
521 self.job_section.add_matched_fw_build(
522 board,
523 self.firmware_rw_build_spec,
524 firmware_rw_build,
525 read_only=False)
526 if firmware_ro_build:
527 self.job_section.add_matched_fw_build(
528 board,
529 self.firmware_ro_build_spec,
530 firmware_ro_build,
531 read_only=True)
532
Xinan Lin6668e0f2020-05-29 10:02:57 -0700533 # Board above is used as build target to control the CrOS image.
534 # The following part is to assign models for lab boards, where
535 # the suffix should be removed.
Prathmesh Prabhu074b8d42020-08-13 17:17:33 +0000536 hwtest_board = build_lib.reshape_board(board)
537
Xinan Lin6668e0f2020-05-29 10:02:57 -0700538 models = models_by_board.get(hwtest_board, [None])
C Shapiro7f24a002017-12-05 14:25:09 -0700539
Harpreet Grewalbbbb7de2019-02-05 19:35:03 +0000540 for model in models:
541 if ((passed_model is not None and model == passed_model) or
542 passed_model is None):
Xinan Lin6668e0f2020-05-29 10:02:57 -0700543 full_model_name = '%s_%s' % (hwtest_board, model)
Xixuan Wua41efa22019-05-17 14:28:04 -0700544 # Respect exclude first.
545 if self.exclude_models and full_model_name in self.exclude_models:
546 logging.debug("Skip model %s as it's in exclude model list %s",
547 model, self.exclude_models)
548 continue
549
550 if self.models and full_model_name not in self.models:
551 logging.debug("Skip model %s as it's not in support model list %s",
552 model, self.models)
553 continue
554
C Shapiro09108252019-08-01 14:52:52 -0500555 explicit_model = model
556
557 if self.any_model:
Xinan Lin3ba18a02019-08-13 15:44:55 -0700558 explicit_model = None
C Shapiro09108252019-08-01 14:52:52 -0500559 unique_build = str(cros_build)
560 if unique_build in model_agnostic_cros_builds:
561 # Skip since we've already run with no explicit model set.
Xinan Lindf0698a2020-02-05 22:38:11 -0800562 msg = "Skip model %s as any_model enabled for this job." % model
563 self.job_section.add_schedule_job(board, model, msg=msg)
C Shapiro09108252019-08-01 14:52:52 -0500564 continue
565 model_agnostic_cros_builds.add(unique_build)
566
Xinan Lindf0698a2020-02-05 22:38:11 -0800567 task_id = str(uuid.uuid1())
Harpreet Grewalbbbb7de2019-02-05 19:35:03 +0000568 self._push_suite(
Xinan Lindf0698a2020-02-05 22:38:11 -0800569 task_id=task_id,
Xixuan Wub4b2f412019-05-03 11:22:31 -0700570 board=hwtest_board,
C Shapiro09108252019-08-01 14:52:52 -0500571 model=explicit_model,
Harpreet Grewalbbbb7de2019-02-05 19:35:03 +0000572 cros_build=cros_build,
573 firmware_rw_build=firmware_rw_build,
574 firmware_ro_build=firmware_ro_build,
Xinan Lin0550f492020-01-21 16:25:53 -0800575 test_source_build=test_source_build)
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700576
Xinan Lin6668e0f2020-05-29 10:02:57 -0700577 # Analytics table stores the build target instead of the lab board.
Xinan Lin0673a182020-04-14 15:09:35 -0700578 self.job_section.add_schedule_job(
579 board, explicit_model, task_id=task_id)
Xinan Lindf0698a2020-02-05 22:38:11 -0800580
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700581 def _schedule_launch_control_builds(self, launch_control_builds):
582 """Schedule tasks with launch control builds.
583
584 Args:
585 launch_control_builds: the build dict for Android boards.
586 """
587 for board, launch_control_build in launch_control_builds.iteritems():
588 logging.debug('Running %s on %s', self.name, board)
Po-Hsien Wang6d589732018-05-15 17:19:34 -0700589 if self.exclude_boards and board in self.exclude_boards:
590 logging.debug('Board %s is in excluded board list: %s',
591 board, self.exclude_boards)
592 continue
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700593 if self.boards and board not in self.boards:
594 logging.debug('Board %s is not in supported board list: %s',
595 board, self.boards)
596 continue
597
598 for android_build in launch_control_build:
599 if not any([branch in android_build
600 for branch in self.launch_control_branches]):
601 logging.debug('Branch %s is not required to run for task '
602 '%s', android_build, self.name)
603 continue
604
605 self._push_suite(board=board,
606 test_source_build=android_build,
607 launch_control_build=android_build)
608
609
610def _pick_branch(build_type, milestone):
611 """Select branch based on build type.
612
613 If the build_type is a bare branch, return build_type as the build spec.
614 If the build_type is a normal CrOS branch, return milestone as the build
615 spec.
616
617 Args:
618 build_type: a string builder name, like 'release'.
619 milestone: a string milestone, like '55'.
620
621 Returns:
622 A string milestone if build_type represents CrOS build, otherwise
623 return build_type.
624 """
625 return build_type if build_type in build_lib.BARE_BRANCHES else milestone
Xinan Linae7d6372019-09-12 14:42:10 -0700626
627
628def _split_unibuilds(build_dict, configs):
629 """Split the uni-builds to all models under a board.
630
631 Args:
632 build_dict: the build dict for ChromeOS boards, see return
Xinan Linea1efcb2019-12-30 23:46:42 -0800633 value of |build_lib.get_cros_builds|.
Xinan Linae7d6372019-09-12 14:42:10 -0700634 configs: a config_reader.Configs object.
635
636 Returns:
637 A build dict.
638 """
639 models_by_board = configs.lab_config.get_cros_model_map()
640 if not models_by_board:
641 return build_dict
642 all_branch_build_dict = {}
643 for (board, model, config, milestone), platform in build_dict.iteritems():
644 uni_build_models = models_by_board.get(board)
645 if uni_build_models is not None and model is None:
646 for uni_build_model in uni_build_models:
647 model_key = (board, uni_build_model, config, milestone)
648 _add_build_dict(all_branch_build_dict, model_key, platform)
649 continue
650 build_key = (board, model, config, milestone)
651 _add_build_dict(all_branch_build_dict, build_key, platform)
652
653 return all_branch_build_dict
654
655
656def _add_build_dict(build_dict, key, value):
657 """A wrapper to add or update an item in build_dict."""
658 cur_manifest = build_dict.get(key)
659 if cur_manifest is None:
660 build_dict[key] = value
661 return
662 build_dict[key] = max(
663 [cur_manifest, value], key=version.LooseVersion)