blob: 2577ff14df05da83ec5dc8fb9dd59969dc6c98e3 [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."""
6# pylint: disable=g-bad-import-order
7
8from distutils import version
9import MySQLdb
10import logging
11
12import build_lib
13import task_executor
Xixuan Wu0c76d5b2017-08-30 16:40:17 -070014import tot_manager
15
16# The max lifetime of a suite scheduled by suite scheduler
17_JOB_MAX_RUNTIME_MINS_DEFAULT = 72 * 60
18
19# One kind of formats for RO firmware spec.
20_RELEASE_RO_FIRMWARE_SPEC = 'released_ro_'
21
22
23class SchedulingError(Exception):
24 """Raised to indicate a failure in scheduling a task."""
25
26
27class Task(object):
28 """Represents an entry from the suite_scheduler config file.
29
30 Each entry from the suite_scheduler config file maps one-to-one to a
31 Task. Each instance has enough information to schedule itself.
32 """
33
34 def __init__(self, task_info, tot=None):
35 """Initialize a task instance.
36
37 Args:
38 task_info: a config_reader.TaskInfo object, which includes:
39 name, name of this task, e.g. 'NightlyPower'
40 suite, the name of the suite to run, e.g. 'graphics_per-day'
41 branch_specs, a pre-vetted iterable of branch specifiers,
42 e.g. ['>=R18', 'factory']
43 pool, the pool of machines to schedule tasks. Default is None.
44 num, the number of devices to shard the test suite. It could
45 be an Integer or None. By default it's None.
46 boards, a comma separated list of boards to run this task on. Default
47 is None, which allows this task to run on all boards.
48 priority, the string name of a priority from constants.Priorities.
49 timeout, the max lifetime of the suite in hours.
50 cros_build_spec, spec used to determine the ChromeOS build to test
51 with a firmware build, e.g., tot, R41 etc.
52 firmware_rw_build_spec, spec used to determine the firmware RW build
53 test with a ChromeOS build.
54 firmware_ro_build_spec, spec used to determine the firmware RO build
55 test with a ChromeOS build.
56 test_source, the source of test code when firmware will be updated in
57 the test. The value can be 'firmware_rw', 'firmware_ro' or 'cros'.
58 job_retry, set to True to enable job-level retry. Default is False.
Xixuan Wu80531932017-10-12 17:26:51 -070059 no_delay, set to True to raise the priority of this task in task.
60 force, set to True to schedule this suite no matter whether there's
61 duplicate jobs before.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -070062 queue, so the suite jobs can start running tests with no waiting.
63 hour, an integer specifying the hour that a nightly run should be
64 triggered, default is set to 21.
65 day, an integer specifying the day of a week that a weekly run should
66 be triggered, default is set to 5 (Saturday).
67 os_type, type of OS, e.g., cros, brillo, android. Default is cros.
68 The argument is required for android/brillo builds.
69 launch_control_branches, comma separated string of launch control
70 branches. The argument is required and only applicable for
71 android/brillo builds.
72 launch_control_targets, comma separated string of build targets for
73 launch control builds. The argument is required and only
74 applicable for android/brillo builds.
75 testbed_dut_count, number of duts to test when using a testbed.
76 tot: The tot manager for checking ToT. If it's None, a new tot_manager
77 instance will be initialized.
78 """
Xixuan Wu5451a662017-10-17 10:57:40 -070079 # Indicate whether there're suites get pushed into taskqueue for this task.
80 self.is_pushed = False
81
Xixuan Wu0c76d5b2017-08-30 16:40:17 -070082 self.name = task_info.name
83 self.suite = task_info.suite
84 self.branch_specs = task_info.branch_specs
85 self.pool = task_info.pool
86 self.num = task_info.num
87 self.priority = task_info.priority
88 self.timeout = task_info.timeout
89 self.cros_build_spec = task_info.cros_build_spec
90 self.firmware_rw_build_spec = task_info.firmware_rw_build_spec
91 self.firmware_ro_build_spec = task_info.firmware_ro_build_spec
92 self.test_source = task_info.test_source
93 self.job_retry = task_info.job_retry
94 self.no_delay = task_info.no_delay
Xixuan Wu80531932017-10-12 17:26:51 -070095 self.force = task_info.force
Xixuan Wu0c76d5b2017-08-30 16:40:17 -070096 self.os_type = task_info.os_type
97 self.testbed_dut_count = task_info.testbed_dut_count
Xixuan Wu008ee832017-10-12 16:59:34 -070098 self.hour = task_info.hour
99 self.day = task_info.day
Craig Bergstrom58263d32018-04-26 14:11:35 -0600100 self.only_hwtest_sanity_required = task_info.only_hwtest_sanity_required
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700101
102 if task_info.lc_branches:
103 self.launch_control_branches = [
104 t.strip() for t in task_info.lc_branches.split(',')]
105 else:
106 self.launch_control_branches = []
107
108 if task_info.lc_targets:
109 self.launch_control_targets = [
110 t.strip() for t in task_info.lc_targets.split(',')]
111 else:
112 self.launch_control_targets = []
113
114 if task_info.boards:
115 self.boards = [t.strip() for t in task_info.boards.split(',')]
116 else:
117 self.boards = []
118
119 if tot is None:
120 self.tot_manager = tot_manager.TotMilestoneManager()
121 else:
122 self.tot_manager = tot
123
124 self._set_spec_compare_info()
125
Craig Bergstrom58263d32018-04-26 14:11:35 -0600126 def schedule(self, launch_control_builds, cros_builds_tuple, lab_config,
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700127 db_client):
128 """Schedule the task by its settings.
129
130 Args:
131 launch_control_builds: the build dict for Android boards, see
132 return value of |get_launch_control_builds|.
Craig Bergstrom58263d32018-04-26 14:11:35 -0600133 cros_builds_tuple: the two-tuple of build dicts for ChromeOS boards,
134 see return value of |get_cros_builds|.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700135 lab_config: a config.LabConfig object, to read lab config file.
136 db_client: a cloud_sql_client.CIDBClient object to connect to cidb.
137
138 Raises:
139 SchedulingError: if tasks that should be scheduled fail to schedule.
Xixuan Wu5451a662017-10-17 10:57:40 -0700140
141 Returns:
142 A boolean indicator to show whether there're suites related to this task
143 get pushed into the suites queue.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700144 """
145 assert lab_config is not None
Xixuan Wu5451a662017-10-17 10:57:40 -0700146 self.is_pushed = False
147
Craig Bergstrom58263d32018-04-26 14:11:35 -0600148 branch_builds, relaxed_builds = cros_builds_tuple
149 builds_dict = branch_builds
150 if self.only_hwtest_sanity_required:
151 builds_dict = relaxed_builds
152
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700153 logging.info('######## Scheduling task %s ########', self.name)
154 if self.os_type == build_lib.OS_TYPE_CROS:
Craig Bergstrom58263d32018-04-26 14:11:35 -0600155 if not builds_dict:
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700156 logging.info('No CrOS build to run, skip running.')
157 else:
Craig Bergstrom58263d32018-04-26 14:11:35 -0600158 self._schedule_cros_builds(builds_dict, lab_config, db_client)
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700159 else:
160 if not launch_control_builds:
161 logging.info('No Android build to run, skip running.')
162 else:
163 self._schedule_launch_control_builds(launch_control_builds)
164
Xixuan Wu5451a662017-10-17 10:57:40 -0700165 return self.is_pushed
166
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700167 def _set_spec_compare_info(self):
168 """Set branch spec compare info for task for further check."""
169 self._bare_branches = []
170 self._version_equal_constraint = False
171 self._version_gte_constraint = False
172 self._version_lte_constraint = False
173
174 if not self.branch_specs:
175 # Any milestone is OK.
176 self._numeric_constraint = version.LooseVersion('0')
177 else:
178 self._numeric_constraint = None
179 for spec in self.branch_specs:
180 if 'tot' in spec.lower():
181 # Convert spec >=tot-1 to >=RXX.
182 tot_str = spec[spec.index('tot'):]
183 spec = spec.replace(
184 tot_str, self.tot_manager.convert_tot_spec(tot_str))
185
186 if spec.startswith('>='):
187 self._numeric_constraint = version.LooseVersion(
188 spec.lstrip('>=R'))
189 self._version_gte_constraint = True
190 elif spec.startswith('<='):
191 self._numeric_constraint = version.LooseVersion(
192 spec.lstrip('<=R'))
193 self._version_lte_constraint = True
194 elif spec.startswith('=='):
195 self._version_equal_constraint = True
196 self._numeric_constraint = version.LooseVersion(
197 spec.lstrip('==R'))
198 else:
199 self._bare_branches.append(spec)
200
201 def _fits_spec(self, branch):
202 """Check if a branch is deemed OK by this task's branch specs.
203
204 Will return whether a branch 'fits' the specifications stored in this task.
205
206 Examples:
207 Assuming tot=R40
208 t = Task('Name', 'suite', ['factory', '>=tot-1'])
209 t._fits_spec('factory') # True
210 t._fits_spec('40') # True
211 t._fits_spec('38') # False
212 t._fits_spec('firmware') # False
213
214 Args:
215 branch: the branch to check.
216
217 Returns:
218 True if branch 'fits' with stored specs, False otherwise.
219 """
220 if branch in build_lib.BARE_BRANCHES:
221 return branch in self._bare_branches
222
223 if self._numeric_constraint:
224 if self._version_equal_constraint:
225 return version.LooseVersion(branch) == self._numeric_constraint
226 elif self._version_gte_constraint:
227 return version.LooseVersion(branch) >= self._numeric_constraint
228 elif self._version_lte_constraint:
229 return version.LooseVersion(branch) <= self._numeric_constraint
230 else:
231 return version.LooseVersion(branch) >= self._numeric_constraint
232 else:
233 return False
234
235 def _get_latest_firmware_build_from_db(self, spec, board, db_client):
236 """Get the latest firmware build from CIDB database.
237
238 Args:
239 spec: a string build spec for RO or RW firmware, eg. firmware,
240 cros. For RO firmware, the value can also be released_ro_X.
241 board: the board against which this task will run suite job.
242 db_client: a cloud_sql_client.CIDBClient object to connect to cidb.
243
244 Returns:
245 the latest firmware build.
246
247 Raises:
248 MySQLdb.OperationalError: if connection operations are not valid.
249 """
250 build_type = 'release' if spec == 'cros' else spec
251 # TODO(xixuan): not all firmware are saved in cidb.
252 build = db_client.get_latest_passed_builds('%s-%s' % (board, build_type))
253
254 if build is None:
255 return None
256 else:
257 latest_build = str(build_lib.CrOSBuild(
258 board, build_type, build.milestone, build.platform))
259 logging.debug('latest firmware build for %s-%s: %s', board,
260 build_type, latest_build)
261 return latest_build
262
263 def _get_latest_firmware_build_from_lab_config(self, spec, board,
264 lab_config):
265 """Get latest firmware from lab config file.
266
267 Args:
268 spec: a string build spec for RO or RW firmware, eg. firmware,
269 cros. For RO firmware, the value can also be released_ro_X.
270 board: a string board against which this task will run suite job.
271 lab_config: a config.LabConfig object, to read lab config file.
272
273 Returns:
274 A string latest firmware build.
275
276 Raises:
277 ValueError: if no firmware build list is found in lab config file.
278 """
279 index = int(spec[len(_RELEASE_RO_FIRMWARE_SPEC):])
280 released_ro_builds = lab_config.get_firmware_ro_build_list(
281 'RELEASED_RO_BUILDS_%s' % board).split(',')
282 if len(released_ro_builds) < index:
283 raise ValueError('No %dth ro_builds in the lab_config firmware '
284 'list %r' % (index, released_ro_builds))
285
286 logging.debug('Get ro_build: %s', released_ro_builds[index - 1])
287 return released_ro_builds[index - 1]
288
289 def _get_firmware_build(self, spec, board, lab_config, db_client):
290 """Get the firmware build name to test with ChromeOS build.
291
292 Args:
293 spec: a string build spec for RO or RW firmware, eg. firmware,
294 cros. For RO firmware, the value can also be released_ro_X.
295 board: a string board against which this task will run suite job.
296 lab_config: a config.LabConfig object, to read lab config file.
297 db_client: a cloud_sql_client.CIDBClient object to connect to cidb.
298
299 Returns:
300 A string firmware build name.
301
302 Raises:
303 ValueError: if failing to get firmware from lab config file;
304 MySQLdb.OperationalError: if DB connection is not available.
305 """
306 if not spec or spec == 'stable':
307 # TODO(crbug.com/577316): Query stable RO firmware.
308 logging.debug('%s RO firmware build is not supported.', spec)
309 return None
310
311 try:
312 if spec.startswith(_RELEASE_RO_FIRMWARE_SPEC):
313 # For RO firmware whose value is released_ro_X, where X is the index of
314 # the list defined in lab config file:
315 # CROS/RELEASED_RO_BUILDS_[board].
316 # For example, for spec 'released_ro_2', and lab config file
317 # CROS/RELEASED_RO_BUILDS_veyron_jerry: build1,build2,
Craig Bergstrom58263d32018-04-26 14:11:35 -0600318 # return firmware RO build should be 'build2'.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700319 return self._get_latest_firmware_build_from_lab_config(
320 spec, board, lab_config)
321 else:
322 return self._get_latest_firmware_build_from_db(spec, board, db_client)
323 except ValueError as e:
324 logging.warning('Failed to get firmware from lab config file'
325 'for spec %s, board %s: %s', spec, board, str(e))
326 return None
327 except MySQLdb.OperationalError as e:
328 logging.warning('Failed to get firmware from cidb for spec %s, '
329 'board %s: %s', spec, board, str(e))
330 return None
331
C Shapiro7f24a002017-12-05 14:25:09 -0700332 def _push_suite(
333 self,
334 board=None,
335 model=None,
336 cros_build=None,
337 firmware_rw_build=None,
338 firmware_ro_build=None,
339 test_source_build=None,
340 launch_control_build=None,
341 run_prod_code=False):
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700342 """Schedule suite job for the task by pushing suites to SuiteQueue.
343
344 Args:
345 board: the board against which this suite job run.
C Shapiro7f24a002017-12-05 14:25:09 -0700346 model: the model name for unibuild.
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700347 cros_build: the CrOS build of this suite job.
348 firmware_rw_build: Firmware RW build to run this suite job with.
349 firmware_ro_build: Firmware RO build to run this suite job with.
350 test_source_build: Test source build, used for server-side
351 packaging of this suite job.
352 launch_control_build: the launch control build of this suite job.
353 run_prod_code: If True, the suite will run the test code that lives
354 in prod aka the test code currently on the lab servers. If
355 False, the control files and test code for this suite run will
356 be retrieved from the build artifacts. Default is False.
357 """
358 android_build = None
359 testbed_build = None
360
361 if self.testbed_dut_count:
362 launch_control_build = '%s#%d' % (launch_control_build,
363 self.testbed_dut_count)
364 test_source_build = launch_control_build
365 board = '%s-%d' % (board, self.testbed_dut_count)
366
367 if launch_control_build:
368 if not self.testbed_dut_count:
369 android_build = launch_control_build
370 else:
371 testbed_build = launch_control_build
372
373 suite_job_parameters = {
374 'suite': self.suite,
375 'board': board,
C Shapiro7f24a002017-12-05 14:25:09 -0700376 'model': model,
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700377 build_lib.BuildVersionKey.CROS_VERSION: cros_build,
378 build_lib.BuildVersionKey.FW_RW_VERSION: firmware_rw_build,
379 build_lib.BuildVersionKey.FW_RO_VERSION: firmware_ro_build,
380 build_lib.BuildVersionKey.ANDROID_BUILD_VERSION: android_build,
381 build_lib.BuildVersionKey.TESTBED_BUILD_VERSION: testbed_build,
382 'num': self.num,
383 'pool': self.pool,
384 'priority': self.priority,
385 'timeout': self.timeout,
386 'timeout_mins': _JOB_MAX_RUNTIME_MINS_DEFAULT,
387 'max_runtime_mins': _JOB_MAX_RUNTIME_MINS_DEFAULT,
Xixuan Wu2ba72652017-09-15 15:49:42 -0700388 'no_wait_for_results': not self.job_retry,
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700389 'test_source_build': test_source_build,
390 'job_retry': self.job_retry,
391 'no_delay': self.no_delay,
Xixuan Wu80531932017-10-12 17:26:51 -0700392 'force': self.force,
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700393 'run_prod_code': run_prod_code,
394 }
395
396 task_executor.push(task_executor.SUITES_QUEUE, **suite_job_parameters)
397 logging.info('Pushing task %r into taskqueue', suite_job_parameters)
Xixuan Wu5451a662017-10-17 10:57:40 -0700398 self.is_pushed = True
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700399
Craig Bergstrom58263d32018-04-26 14:11:35 -0600400 def _schedule_cros_builds(self, build_dict, lab_config, db_client):
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700401 """Schedule tasks with branch builds.
402
403 Args:
Craig Bergstrom58263d32018-04-26 14:11:35 -0600404 build_dict: the build dict for ChromeOS boards, see return
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700405 value of |build_lib.get_cros_builds_since_date_from_db|.
406 lab_config: a config.LabConfig object, to read lab config file.
407 db_client: a cloud_sql_client.CIDBClient object to connect to cidb.
408 """
C Shapiro7f24a002017-12-05 14:25:09 -0700409 models_by_board = lab_config.get_cros_model_map() if lab_config else {}
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700410 for (board, build_type,
Craig Bergstrom58263d32018-04-26 14:11:35 -0600411 milestone), manifest in build_dict.iteritems():
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700412 cros_build = str(build_lib.CrOSBuild(board, build_type, milestone,
413 manifest))
414 logging.info('Running %s on %s', self.name, cros_build)
415 if self.boards and board not in self.boards:
416 logging.debug('Board %s is not in supported board list: %s',
417 board, self.boards)
418 continue
419
420 # Check the fitness of the build's branch for task
421 branch_build_spec = _pick_branch(build_type, milestone)
422 if not self._fits_spec(branch_build_spec):
423 logging.debug("branch_build spec %s doesn't fit this task's "
424 "requirement: %s", branch_build_spec, self.branch_specs)
425 continue
426
427 firmware_rw_build = None
428 firmware_ro_build = None
429 if self.firmware_rw_build_spec or self.firmware_ro_build_spec:
430 firmware_rw_build = self._get_firmware_build(
431 self.firmware_rw_build_spec, board, lab_config, db_client)
432 firmware_ro_build = self._get_firmware_build(
433 self.firmware_ro_build_spec, board, lab_config, db_client)
434
435 if not firmware_ro_build and self.firmware_ro_build_spec:
436 logging.debug('No firmware ro build to run, skip running')
437 continue
438
439 if self.test_source == build_lib.BuildType.FIRMWARE_RW:
440 test_source_build = firmware_rw_build
441 else:
442 # Default test source build to CrOS build if it's not specified and
443 # run_prod_code is set to False, which is the case in current suite
444 # scheduler settings, where run_prod_code is always False.
445 test_source_build = cros_build
446
C Shapiro7f24a002017-12-05 14:25:09 -0700447 models = models_by_board.get(board, [None])
448
449 for model in models:
450 self._push_suite(board=board,
451 model=model,
452 cros_build=cros_build,
453 firmware_rw_build=firmware_rw_build,
454 firmware_ro_build=firmware_ro_build,
455 test_source_build=test_source_build)
Xixuan Wu0c76d5b2017-08-30 16:40:17 -0700456
457 def _schedule_launch_control_builds(self, launch_control_builds):
458 """Schedule tasks with launch control builds.
459
460 Args:
461 launch_control_builds: the build dict for Android boards.
462 """
463 for board, launch_control_build in launch_control_builds.iteritems():
464 logging.debug('Running %s on %s', self.name, board)
465 if self.boards and board not in self.boards:
466 logging.debug('Board %s is not in supported board list: %s',
467 board, self.boards)
468 continue
469
470 for android_build in launch_control_build:
471 if not any([branch in android_build
472 for branch in self.launch_control_branches]):
473 logging.debug('Branch %s is not required to run for task '
474 '%s', android_build, self.name)
475 continue
476
477 self._push_suite(board=board,
478 test_source_build=android_build,
479 launch_control_build=android_build)
480
481
482def _pick_branch(build_type, milestone):
483 """Select branch based on build type.
484
485 If the build_type is a bare branch, return build_type as the build spec.
486 If the build_type is a normal CrOS branch, return milestone as the build
487 spec.
488
489 Args:
490 build_type: a string builder name, like 'release'.
491 milestone: a string milestone, like '55'.
492
493 Returns:
494 A string milestone if build_type represents CrOS build, otherwise
495 return build_type.
496 """
497 return build_type if build_type in build_lib.BARE_BRANCHES else milestone