blob: 013f2fbb81760e4065228491916e4e094e1f2983 [file] [log] [blame]
Chris Masonefad911a2012-03-29 12:30:26 -07001# Copyright (c) 2012 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
Chris Masone67f06d62012-04-12 15:16:56 -07005
Prashanth Balasubramanian1b859622014-10-28 16:02:15 -07006import logging
7import re
8import subprocess
Dan Shia25e0d42015-07-23 15:00:04 -07009
10import base_event
Aviv Keshetd83ef442013-01-16 16:19:35 -080011import deduping_scheduler
Alex Millerc7bcf8b2013-09-07 20:13:20 -070012import driver
Dan Shia25e0d42015-07-23 15:00:04 -070013import manifest_versions
Chris Masone67f06d62012-04-12 15:16:56 -070014from distutils import version
Alex Miller511a9e32012-07-03 09:16:47 -070015from constants import Labels
Dan Shia25e0d42015-07-23 15:00:04 -070016from constants import Builds
Chris Masone96f16632012-04-04 18:36:03 -070017
Prashanth Balasubramanianf571aa62014-10-13 18:09:44 -070018import common
Fang Dengf08814a2015-08-03 18:12:18 +000019from autotest_lib.server import utils as server_utils
Prashanth Balasubramanianf571aa62014-10-13 18:09:44 -070020from autotest_lib.server.cros.dynamic_suite import constants
Prashanth Balasubramanianf571aa62014-10-13 18:09:44 -070021
Chris Masone96f16632012-04-04 18:36:03 -070022
23class MalformedConfigEntry(Exception):
24 """Raised to indicate a failure to parse a Task out of a config."""
25 pass
Chris Masonefad911a2012-03-29 12:30:26 -070026
27
Alex Miller0d003572013-03-18 11:51:30 -070028BARE_BRANCHES = ['factory', 'firmware']
Chris Masone67f06d62012-04-12 15:16:56 -070029
30
31def PickBranchName(type, milestone):
Dan Shiaceb91d2013-02-20 12:41:28 -080032 """Pick branch name. If type is among BARE_BRANCHES, return type,
33 otherwise, return milestone.
34
35 @param type: type of the branch, e.g., 'release', 'factory', or 'firmware'
36 @param milestone: CrOS milestone number
37 """
Chris Masone67f06d62012-04-12 15:16:56 -070038 if type in BARE_BRANCHES:
39 return type
40 return milestone
41
42
Prashanth Balasubramanianf571aa62014-10-13 18:09:44 -070043class TotMilestoneManager(object):
44 """A class capable of converting tot string to milestone numbers.
45
46 This class is used as a cache for the tot milestone, so we don't
47 repeatedly hit google storage for all O(100) tasks in suite
48 scheduler's ini file.
49 """
50
Fang Dengf08814a2015-08-03 18:12:18 +000051 __metaclass__ = server_utils.Singleton
Prashanth Balasubramanianf571aa62014-10-13 18:09:44 -070052
Dan Shi23245142015-01-22 13:22:28 -080053 # True if suite_scheduler is running for sanity check. When it's set to
54 # True, the code won't make gsutil call to get the actual tot milestone to
55 # avoid dependency on the installation of gsutil to run sanity check.
56 is_sanity = False
57
58
Prashanth Balasubramanianf571aa62014-10-13 18:09:44 -070059 @staticmethod
60 def _tot_milestone():
61 """Get the tot milestone, eg: R40
62
63 @returns: A string representing the Tot milestone as declared by
64 the LATEST_BUILD_URL, or an empty string if LATEST_BUILD_URL
65 doesn't exist.
66 """
Dan Shi23245142015-01-22 13:22:28 -080067 if TotMilestoneManager.is_sanity:
68 logging.info('suite_scheduler is running for sanity purpose, no '
69 'need to get the actual tot milestone string.')
70 return 'R40'
71
Prashanth Balasubramanian1b859622014-10-28 16:02:15 -070072 cmd = ['gsutil', 'cat', constants.LATEST_BUILD_URL]
73 proc = subprocess.Popen(
74 cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
75 stdout, stderr = proc.communicate()
76 if proc.poll():
77 logging.warning('Failed to get latest build: %s', stderr)
Prashanth Balasubramanianf571aa62014-10-13 18:09:44 -070078 return ''
Prashanth Balasubramanian1b859622014-10-28 16:02:15 -070079 return stdout.split('-')[0]
Prashanth Balasubramanianf571aa62014-10-13 18:09:44 -070080
81
82 def refresh(self):
83 """Refresh the tot milestone string managed by this class."""
84 self.tot = self._tot_milestone()
85
86
87 def __init__(self):
88 """Initialize a TotMilestoneManager."""
89 self.refresh()
90
91
92 def ConvertTotSpec(self, tot_spec):
93 """Converts a tot spec to the appropriate milestone.
94
95 Assume tot is R40:
96 tot -> R40
97 tot-1 -> R39
98 tot-2 -> R38
99 tot-(any other numbers) -> R40
100
101 With the last option one assumes that a malformed configuration that has
102 'tot' in it, wants at least tot.
103
104 @param tot_spec: A string representing the tot spec.
105 @raises MalformedConfigEntry: If the tot_spec doesn't match the
106 expected format.
107 """
108 tot_spec = tot_spec.lower()
109 match = re.match('(tot)[-]?(1$|2$)?', tot_spec)
110 if not match:
111 raise MalformedConfigEntry(
112 "%s isn't a valid branch spec." % tot_spec)
113 tot_mstone = self.tot
114 num_back = match.groups()[1]
115 if num_back:
116 tot_mstone_num = tot_mstone.lstrip('R')
117 tot_mstone = tot_mstone.replace(
118 tot_mstone_num, str(int(tot_mstone_num)-int(num_back)))
119 return tot_mstone
120
121
Chris Masone013859b2012-04-01 13:45:26 -0700122class Task(object):
Chris Masonefad911a2012-03-29 12:30:26 -0700123 """Represents an entry from the scheduler config. Can schedule itself.
124
125 Each entry from the scheduler config file maps one-to-one to a
Chris Masone013859b2012-04-01 13:45:26 -0700126 Task. Each instance has enough info to schedule itself
Chris Masonefad911a2012-03-29 12:30:26 -0700127 on-demand with the AFE.
128
Aviv Keshet52c7a212015-12-07 15:27:22 -0800129 This class also overrides __hash__() and all comparator methods to enable
Chris Masonefad911a2012-03-29 12:30:26 -0700130 correct use in dicts, sets, etc.
131 """
132
Chris Masone96f16632012-04-04 18:36:03 -0700133
134 @staticmethod
135 def CreateFromConfigSection(config, section):
136 """Create a Task from a section of a config file.
137
138 The section to parse should look like this:
139 [TaskName]
140 suite: suite_to_run # Required
141 run_on: event_on which to run # Required
Dan Shiaceb91d2013-02-20 12:41:28 -0800142 branch_specs: factory,firmware,>=R12 or ==R12 # Optional
Chris Masone96f16632012-04-04 18:36:03 -0700143 pool: pool_of_devices # Optional
Chris Masone3eeaf0a2012-08-09 14:07:27 -0700144 num: sharding_factor # int, Optional
Alex Millerf2b57442013-09-07 18:40:02 -0700145 boards: board1, board2 # comma seperated string, Optional
Chris Masone96f16632012-04-04 18:36:03 -0700146
Chris Masone67f06d62012-04-12 15:16:56 -0700147 By default, Tasks run on all release branches, not factory or firmware.
148
Chris Masone96f16632012-04-04 18:36:03 -0700149 @param config: a ForgivingConfigParser.
150 @param section: the section to parse into a Task.
151 @return keyword, Task object pair. One or both will be None on error.
152 @raise MalformedConfigEntry if there's a problem parsing |section|.
153 """
Alex Miller06695022012-07-18 09:31:36 -0700154 if not config.has_section(section):
155 raise MalformedConfigEntry('unknown section %s' % section)
156
Alex Millerf2b57442013-09-07 18:40:02 -0700157 allowed = set(['suite', 'run_on', 'branch_specs', 'pool', 'num',
Dan Shia25e0d42015-07-23 15:00:04 -0700158 'boards', 'file_bugs', 'cros_build_spec',
Dan Shi29a16992015-09-22 11:29:58 -0700159 'firmware_rw_build_spec', 'test_source', 'job_retry'])
Alex Millerbb535e22012-07-11 20:11:33 -0700160 # The parameter of union() is the keys under the section in the config
161 # The union merges this with the allowed set, so if any optional keys
162 # are omitted, then they're filled in. If any extra keys are present,
163 # then they will expand unioned set, causing it to fail the following
164 # comparison against the allowed set.
165 section_headers = allowed.union(dict(config.items(section)).keys())
166 if allowed != section_headers:
167 raise MalformedConfigEntry('unknown entries: %s' %
168 ", ".join(map(str, section_headers.difference(allowed))))
169
Chris Masone96f16632012-04-04 18:36:03 -0700170 keyword = config.getstring(section, 'run_on')
171 suite = config.getstring(section, 'suite')
172 branches = config.getstring(section, 'branch_specs')
173 pool = config.getstring(section, 'pool')
Alex Millerf2b57442013-09-07 18:40:02 -0700174 boards = config.getstring(section, 'boards')
Prashanth B6de2bde2014-03-25 18:45:02 -0700175 file_bugs = config.getboolean(section, 'file_bugs')
Dan Shia25e0d42015-07-23 15:00:04 -0700176 cros_build_spec = config.getstring(section, 'cros_build_spec')
177 firmware_rw_build_spec = config.getstring(
178 section, 'firmware_rw_build_spec')
179 test_source = config.getstring(section, 'test_source')
Dan Shi29a16992015-09-22 11:29:58 -0700180 job_retry = config.getboolean(section, 'job_retry')
Alex Millerc7bcf8b2013-09-07 20:13:20 -0700181 for klass in driver.Driver.EVENT_CLASSES:
182 if klass.KEYWORD == keyword:
183 priority = klass.PRIORITY
184 timeout = klass.TIMEOUT
185 break
186 else:
187 priority = None
188 timeout = None
Chris Masone3eeaf0a2012-08-09 14:07:27 -0700189 try:
190 num = config.getint(section, 'num')
191 except ValueError as e:
192 raise MalformedConfigEntry("Ill-specified 'num': %r" %e)
Chris Masone96f16632012-04-04 18:36:03 -0700193 if not keyword:
194 raise MalformedConfigEntry('No event to |run_on|.')
195 if not suite:
196 raise MalformedConfigEntry('No |suite|')
197 specs = []
198 if branches:
199 specs = re.split('\s*,\s*', branches)
200 Task.CheckBranchSpecs(specs)
Alex Millerc7bcf8b2013-09-07 20:13:20 -0700201 return keyword, Task(section, suite, specs, pool, num, boards,
Prashanth B6de2bde2014-03-25 18:45:02 -0700202 priority, timeout,
Dan Shia25e0d42015-07-23 15:00:04 -0700203 file_bugs=file_bugs if file_bugs else False,
204 cros_build_spec=cros_build_spec,
205 firmware_rw_build_spec=firmware_rw_build_spec,
Dan Shi29a16992015-09-22 11:29:58 -0700206 test_source=test_source, job_retry=job_retry)
Chris Masone96f16632012-04-04 18:36:03 -0700207
208
209 @staticmethod
210 def CheckBranchSpecs(branch_specs):
211 """Make sure entries in the list branch_specs are correctly formed.
212
Chris Masone67f06d62012-04-12 15:16:56 -0700213 We accept any of BARE_BRANCHES in |branch_specs|, as
Dan Shiaceb91d2013-02-20 12:41:28 -0800214 well as _one_ string of the form '>=RXX' or '==RXX', where 'RXX' is a
Chris Masone96f16632012-04-04 18:36:03 -0700215 CrOS milestone number.
216
217 @param branch_specs: an iterable of branch specifiers.
218 @raise MalformedConfigEntry if there's a problem parsing |branch_specs|.
219 """
220 have_seen_numeric_constraint = False
221 for branch in branch_specs:
Chris Masone67f06d62012-04-12 15:16:56 -0700222 if branch in BARE_BRANCHES:
Chris Masone96f16632012-04-04 18:36:03 -0700223 continue
Prashanth Balasubramanianf571aa62014-10-13 18:09:44 -0700224 if not have_seen_numeric_constraint:
225 #TODO(beeps): Why was <= dropped on the floor?
226 if branch.startswith('>=R') or branch.startswith('==R'):
227 have_seen_numeric_constraint = True
228 elif 'tot' in branch:
229 TotMilestoneManager().ConvertTotSpec(
230 branch[branch.index('tot'):])
231 have_seen_numeric_constraint = True
Chris Masone96f16632012-04-04 18:36:03 -0700232 continue
Chris Masone97cf0a72012-05-16 09:55:52 -0700233 raise MalformedConfigEntry("%s isn't a valid branch spec." % branch)
Chris Masone96f16632012-04-04 18:36:03 -0700234
235
Alex Millerf2b57442013-09-07 18:40:02 -0700236 def __init__(self, name, suite, branch_specs, pool=None, num=None,
Dan Shia25e0d42015-07-23 15:00:04 -0700237 boards=None, priority=None, timeout=None, file_bugs=False,
238 cros_build_spec=None, firmware_rw_build_spec=None,
Dan Shi29a16992015-09-22 11:29:58 -0700239 test_source=None, job_retry=False):
Chris Masonefad911a2012-03-29 12:30:26 -0700240 """Constructor
241
Chris Masone96f16632012-04-04 18:36:03 -0700242 Given an iterable in |branch_specs|, pre-vetted using CheckBranchSpecs,
243 we'll store them such that _FitsSpec() can be used to check whether a
244 given branch 'fits' with the specifications passed in here.
245 For example, given branch_specs = ['factory', '>=R18'], we'd set things
246 up so that _FitsSpec() would return True for 'factory', or 'RXX'
Dan Shiaceb91d2013-02-20 12:41:28 -0800247 where XX is a number >= 18. Same check is done for branch_specs = [
248 'factory', '==R18'], which limit the test to only one specific branch.
Chris Masone96f16632012-04-04 18:36:03 -0700249
250 Given branch_specs = ['factory', 'firmware'], _FitsSpec()
251 would pass only those two specific strings.
252
253 Example usage:
Chris Masonecc4631d2012-04-20 12:06:39 -0700254 t = Task('Name', 'suite', ['factory', '>=R18'])
Chris Masone96f16632012-04-04 18:36:03 -0700255 t._FitsSpec('factory') # True
256 t._FitsSpec('R19') # True
257 t._FitsSpec('R17') # False
258 t._FitsSpec('firmware') # False
259 t._FitsSpec('goober') # False
260
Dan Shiaceb91d2013-02-20 12:41:28 -0800261 t = Task('Name', 'suite', ['factory', '==R18'])
262 t._FitsSpec('R19') # False, branch does not equal to 18
263 t._FitsSpec('R18') # True
264 t._FitsSpec('R17') # False
265
Dan Shia25e0d42015-07-23 15:00:04 -0700266 cros_build_spec and firmware_rw_build_spec are set for tests require
267 firmware update on the dut. Only one of them can be set.
268 For example:
269 branch_specs: ==tot
270 firmware_rw_build_spec: firmware
271 test_source: cros
272 This will run test using latest build on firmware branch, and the latest
273 ChromeOS build on ToT. The test source build is ChromeOS build.
274
275 branch_specs: firmware
276 cros_build_spec: ==tot-1
277 test_source: firmware_rw
278 This will run test using latest build on firmware branch, and the latest
279 ChromeOS build on dev channel (ToT-1). The test source build is the
280 firmware RW build.
281
Chris Masone3eeaf0a2012-08-09 14:07:27 -0700282 @param name: name of this task, e.g. 'NightlyPower'
Chris Masonefad911a2012-03-29 12:30:26 -0700283 @param suite: the name of the suite to run, e.g. 'bvt'
Chris Masone96f16632012-04-04 18:36:03 -0700284 @param branch_specs: a pre-vetted iterable of branch specifiers,
285 e.g. ['>=R18', 'factory']
Chris Masonefad911a2012-03-29 12:30:26 -0700286 @param pool: the pool of machines to use for scheduling purposes.
287 Default: None
Chris Masone3eeaf0a2012-08-09 14:07:27 -0700288 @param num: the number of devices across which to shard the test suite.
Aviv Keshetd83ef442013-01-16 16:19:35 -0800289 Type: integer or None
Chris Masone3eeaf0a2012-08-09 14:07:27 -0700290 Default: None
Alex Millerf2b57442013-09-07 18:40:02 -0700291 @param boards: A comma seperated list of boards to run this task on.
292 Default: Run on all boards.
Alex Millerc7bcf8b2013-09-07 20:13:20 -0700293 @param priority: The string name of a priority from
294 client.common_lib.priorities.Priority.
295 @param timeout: The max lifetime of the suite in hours.
Prashanth B6de2bde2014-03-25 18:45:02 -0700296 @param file_bugs: True if bug filing is desired for the suite created
297 for this task.
Dan Shia25e0d42015-07-23 15:00:04 -0700298 @param cros_build_spec: Spec used to determine the ChromeOS build to
299 test with a firmware build, e.g., tot, R41 etc.
300 @param firmware_rw_build_spec: Spec used to determine the firmware build
301 test with a ChromeOS build.
302 @param test_source: The source of test code when firmware will be
303 updated in the test. The value can be `firmware_rw`
304 or `cros`.
Dan Shi29a16992015-09-22 11:29:58 -0700305 @param job_retry: Set to True to enable job-level retry. Default is
306 False.
Chris Masonefad911a2012-03-29 12:30:26 -0700307 """
Chris Masonecc4631d2012-04-20 12:06:39 -0700308 self._name = name
Chris Masonefad911a2012-03-29 12:30:26 -0700309 self._suite = suite
Chris Masone96f16632012-04-04 18:36:03 -0700310 self._branch_specs = branch_specs
Chris Masonefad911a2012-03-29 12:30:26 -0700311 self._pool = pool
Aviv Keshetd83ef442013-01-16 16:19:35 -0800312 self._num = num
Alex Millerc7bcf8b2013-09-07 20:13:20 -0700313 self._priority = priority
314 self._timeout = timeout
Prashanth B6de2bde2014-03-25 18:45:02 -0700315 self._file_bugs = file_bugs
Dan Shia25e0d42015-07-23 15:00:04 -0700316 self._cros_build_spec = cros_build_spec
317 self._firmware_rw_build_spec = firmware_rw_build_spec
318 self._test_source = test_source
Dan Shi29a16992015-09-22 11:29:58 -0700319 self._job_retry = job_retry
Dan Shia25e0d42015-07-23 15:00:04 -0700320
321 if ((self._firmware_rw_build_spec or cros_build_spec) and
322 not self.test_source in [Builds.FIRMWARE_RW, Builds.CROS]):
323 raise MalformedConfigEntry(
324 'You must specify the build for test source. It can only '
325 'be `firmware_rw` or `cros`.')
326 if self._firmware_rw_build_spec and cros_build_spec:
327 raise MalformedConfigEntry(
328 'You cannot specify both firmware_rw_build_spec and '
329 'cros_build_spec. firmware_rw_build_spec is used to specify'
330 ' a firmware build when the suite requires firmware to be '
331 'updated in the dut, its value can only be `firmware`. '
332 'cros_build_spec is used to specify a ChromeOS build when '
333 'build_specs is set to firmware.')
334 if (self._firmware_rw_build_spec and
335 self._firmware_rw_build_spec != 'firmware'):
336 raise MalformedConfigEntry(
337 'firmware_rw_build_spec can only be empty or firmware. It '
338 'does not support other build type yet.')
Chris Masone96f16632012-04-04 18:36:03 -0700339
340 self._bare_branches = []
Dan Shiaceb91d2013-02-20 12:41:28 -0800341 self._version_equal_constraint = False
Dan Shibde10772015-08-18 10:15:58 -0700342 self._version_gte_constraint = False
343 self._version_lte_constraint = False
Chris Masone67f06d62012-04-12 15:16:56 -0700344 if not branch_specs:
345 # Any milestone is OK.
346 self._numeric_constraint = version.LooseVersion('0')
347 else:
348 self._numeric_constraint = None
349 for spec in branch_specs:
Prashanth Balasubramanianf571aa62014-10-13 18:09:44 -0700350 if 'tot' in spec.lower():
351 tot_str = spec[spec.index('tot'):]
352 spec = spec.replace(
353 tot_str, TotMilestoneManager().ConvertTotSpec(
Dan Shibde10772015-08-18 10:15:58 -0700354 tot_str))
Chris Masone67f06d62012-04-12 15:16:56 -0700355 if spec.startswith('>='):
356 self._numeric_constraint = version.LooseVersion(
Dan Shibde10772015-08-18 10:15:58 -0700357 spec.lstrip('>=R'))
358 self._version_gte_constraint = True
359 elif spec.startswith('<='):
360 self._numeric_constraint = version.LooseVersion(
361 spec.lstrip('<=R'))
362 self._version_lte_constraint = True
Dan Shiaceb91d2013-02-20 12:41:28 -0800363 elif spec.startswith('=='):
364 self._version_equal_constraint = True
365 self._numeric_constraint = version.LooseVersion(
Dan Shibde10772015-08-18 10:15:58 -0700366 spec.lstrip('==R'))
Chris Masone67f06d62012-04-12 15:16:56 -0700367 else:
368 self._bare_branches.append(spec)
Alex Millerf2b57442013-09-07 18:40:02 -0700369
Aviv Keshet52c7a212015-12-07 15:27:22 -0800370 # Since we expect __hash__() and other comparator methods to be used
Chris Masonefad911a2012-03-29 12:30:26 -0700371 # frequently by set operations, and they use str() a lot, pre-compute
372 # the string representation of this object.
Aviv Keshet8ce3c982013-01-24 09:23:13 -0800373 if num is None:
374 numStr = '[Default num]'
375 else:
376 numStr = '%d' % num
Alex Millerf2b57442013-09-07 18:40:02 -0700377
378 if boards is None:
379 self._boards = set()
380 boardsStr = '[All boards]'
381 else:
382 self._boards = set([x.strip() for x in boards.split(',')])
383 boardsStr = boards
384
Prashanth B6de2bde2014-03-25 18:45:02 -0700385 self._str = ('%s: %s on %s with pool %s, boards [%s], file_bugs = %s '
386 'across %s machines.' % (self.__class__.__name__,
387 suite, branch_specs, pool, boardsStr, self._file_bugs,
388 numStr))
Chris Masone96f16632012-04-04 18:36:03 -0700389
390
391 def _FitsSpec(self, branch):
392 """Checks if a branch is deemed OK by this instance's branch specs.
393
394 When called on a branch name, will return whether that branch
Dan Shiaceb91d2013-02-20 12:41:28 -0800395 'fits' the specifications stored in self._bare_branches,
Dan Shibde10772015-08-18 10:15:58 -0700396 self._numeric_constraint, self._version_equal_constraint,
397 self._version_gte_constraint and self._version_lte_constraint.
Chris Masone96f16632012-04-04 18:36:03 -0700398
399 @param branch: the branch to check.
400 @return True if b 'fits' with stored specs, False otherwise.
401 """
Chris Masone657e1552012-05-30 17:06:20 -0700402 if branch in BARE_BRANCHES:
403 return branch in self._bare_branches
Dan Shiaceb91d2013-02-20 12:41:28 -0800404 if self._numeric_constraint:
405 if self._version_equal_constraint:
406 return version.LooseVersion(branch) == self._numeric_constraint
Dan Shibde10772015-08-18 10:15:58 -0700407 elif self._version_gte_constraint:
408 return version.LooseVersion(branch) >= self._numeric_constraint
409 elif self._version_lte_constraint:
410 return version.LooseVersion(branch) <= self._numeric_constraint
Dan Shiaceb91d2013-02-20 12:41:28 -0800411 else:
Dan Shibde10772015-08-18 10:15:58 -0700412 # Default to great or equal constraint.
Dan Shiaceb91d2013-02-20 12:41:28 -0800413 return version.LooseVersion(branch) >= self._numeric_constraint
414 else:
415 return False
Chris Masonefad911a2012-03-29 12:30:26 -0700416
417
418 @property
Alex Miller9979b5a2012-11-01 17:36:12 -0700419 def name(self):
Dan Shiaceb91d2013-02-20 12:41:28 -0800420 """Name of this task, e.g. 'NightlyPower'."""
Alex Miller9979b5a2012-11-01 17:36:12 -0700421 return self._name
422
423
424 @property
Chris Masonefad911a2012-03-29 12:30:26 -0700425 def suite(self):
Dan Shiaceb91d2013-02-20 12:41:28 -0800426 """Name of the suite to run, e.g. 'bvt'."""
Chris Masonefad911a2012-03-29 12:30:26 -0700427 return self._suite
428
429
430 @property
Chris Masone96f16632012-04-04 18:36:03 -0700431 def branch_specs(self):
Dan Shiaceb91d2013-02-20 12:41:28 -0800432 """a pre-vetted iterable of branch specifiers,
433 e.g. ['>=R18', 'factory']."""
Chris Masone96f16632012-04-04 18:36:03 -0700434 return self._branch_specs
Chris Masonefad911a2012-03-29 12:30:26 -0700435
436
437 @property
Chris Masone3fba86f2012-04-03 10:06:56 -0700438 def pool(self):
Dan Shiaceb91d2013-02-20 12:41:28 -0800439 """The pool of machines to use for scheduling purposes."""
Chris Masone3fba86f2012-04-03 10:06:56 -0700440 return self._pool
Chris Masonefad911a2012-03-29 12:30:26 -0700441
442
Alex Miller9979b5a2012-11-01 17:36:12 -0700443 @property
444 def num(self):
Dan Shiaceb91d2013-02-20 12:41:28 -0800445 """The number of devices across which to shard the test suite.
446 Type: integer or None"""
Alex Miller9979b5a2012-11-01 17:36:12 -0700447 return self._num
448
449
Alex Millerf2b57442013-09-07 18:40:02 -0700450 @property
451 def boards(self):
452 """The boards on which to run this suite.
453 Type: Iterable of strings"""
454 return self._boards
455
456
Alex Millerc7bcf8b2013-09-07 20:13:20 -0700457 @property
458 def priority(self):
459 """The priority of the suite"""
460 return self._priority
461
462
463 @property
464 def timeout(self):
465 """The maximum lifetime of the suite in hours."""
466 return self._timeout
467
468
Dan Shia25e0d42015-07-23 15:00:04 -0700469 @property
470 def cros_build_spec(self):
471 """The build spec of ChromeOS to test with a firmware build."""
472 return self._cros_build_spec
473
474
475 @property
476 def firmware_rw_build_spec(self):
477 """The build spec of firmware to test with a ChromeOS build."""
478 return self._firmware_rw_build_spec
479
480
481 @property
482 def test_source(self):
483 """Source of the test code, value can be `firmware_rw` or `cros`."""
484 return self._test_source
485
486
Chris Masonefad911a2012-03-29 12:30:26 -0700487 def __str__(self):
488 return self._str
489
490
Chris Masone3eeaf0a2012-08-09 14:07:27 -0700491 def __repr__(self):
492 return self._str
493
494
Chris Masonefad911a2012-03-29 12:30:26 -0700495 def __lt__(self, other):
496 return str(self) < str(other)
497
498
499 def __le__(self, other):
500 return str(self) <= str(other)
501
502
503 def __eq__(self, other):
504 return str(self) == str(other)
505
506
507 def __ne__(self, other):
508 return str(self) != str(other)
509
510
511 def __gt__(self, other):
512 return str(self) > str(other)
513
514
515 def __ge__(self, other):
516 return str(self) >= str(other)
517
518
519 def __hash__(self):
520 """Allows instances to be correctly deduped when used in a set."""
521 return hash(str(self))
522
523
Dan Shia25e0d42015-07-23 15:00:04 -0700524 def _GetCrOSBuild(self, mv, board):
525 """Get the ChromeOS build name to test with firmware build.
526
527 The ChromeOS build to be used is determined by `self.cros_build_spec`.
528 Its value can be:
529 tot: use the latest ToT build.
530 tot-x: use the latest build in x milestone before ToT.
531 Rxx: use the latest build on xx milestone.
532
533 @param board: the board against which to run self._suite.
534 @param mv: an instance of manifest_versions.ManifestVersions.
535
536 @return: The ChromeOS build name to test with firmware build.
537
538 """
539 if not self.cros_build_spec:
540 return None
541 if self.cros_build_spec.startswith('tot'):
542 milestone = TotMilestoneManager().ConvertTotSpec(
543 self.cros_build_spec)[1:]
544 elif self.cros_build_spec.startswith('R'):
545 milestone = self.cros_build_spec[1:]
546 milestone, latest_manifest = mv.GetLatestManifest(
547 board, 'release', milestone=milestone)
548 latest_build = base_event.BuildName(board, 'release', milestone,
549 latest_manifest)
550 logging.debug('Found latest build of %s for spec %s: %s',
551 board, self.cros_build_spec, latest_build)
552 return latest_build
553
554
555 def _GetFirmwareRWBuild(self, mv, board, build_type):
556 """Get the firmware rw build name to test with ChromeOS build.
557
558 The firmware rw build to be used is determined by
559 `self.firmware_rw_build_spec`. Its value can be `firmware` or empty:
560 firmware: use the ToT build in firmware branch.
561
562 @param mv: an instance of manifest_versions.ManifestVersions.
563 @param board: the board against which to run self._suite.
564 @param build_type: Build type of the firmware build, e.g., factory or
565 firmware.
566
567 @return: The firmware rw build name to test with ChromeOS build.
568
569 """
570 if not self.firmware_rw_build_spec:
571 return None
572 latest_milestone, latest_manifest = mv.GetLatestManifest(
573 board, build_type)
574 latest_build = base_event.BuildName(board, build_type, latest_milestone,
575 latest_manifest)
576 logging.debug('Found latest firmware build of %s for spec %s: %s',
577 board, self.firmware_rw_build_spec, latest_build)
578 return latest_build
579
580
Alex Miller7ce1b362012-07-10 09:24:10 -0700581 def AvailableHosts(self, scheduler, board):
582 """Query what hosts are able to run a test on a board and pool
583 combination.
Alex Miller511a9e32012-07-03 09:16:47 -0700584
585 @param scheduler: an instance of DedupingScheduler, as defined in
586 deduping_scheduler.py
587 @param board: the board against which one wants to run the test.
Alex Miller7ce1b362012-07-10 09:24:10 -0700588 @return The list of hosts meeting the board and pool requirements,
589 or None if no hosts were found."""
Alex Millerf2b57442013-09-07 18:40:02 -0700590 if self._boards and board not in self._boards:
591 return []
592
Alex Miller511a9e32012-07-03 09:16:47 -0700593 labels = [Labels.BOARD_PREFIX + board]
594 if self._pool:
Chris Masonecd214e02012-07-10 16:22:10 -0700595 labels.append(Labels.POOL_PREFIX + self._pool)
Alex Miller511a9e32012-07-03 09:16:47 -0700596
Prashanth B6de2bde2014-03-25 18:45:02 -0700597 return scheduler.CheckHostsExist(multiple_labels=labels)
Alex Miller511a9e32012-07-03 09:16:47 -0700598
599
Alex Millerd621cf22012-07-11 13:57:10 -0700600 def ShouldHaveAvailableHosts(self):
601 """As a sanity check, return true if we know for certain that
602 we should be able to schedule this test. If we claim this test
603 should be able to run, and it ends up not being scheduled, then
604 a warning will be reported.
605
606 @return True if this test should be able to run, False otherwise.
607 """
608 return self._pool == 'bvt'
609
610
Dan Shia25e0d42015-07-23 15:00:04 -0700611 def Run(self, scheduler, branch_builds, board, force=False, mv=None):
Chris Masone013859b2012-04-01 13:45:26 -0700612 """Run this task. Returns False if it should be destroyed.
Chris Masonefad911a2012-03-29 12:30:26 -0700613
Chris Masone013859b2012-04-01 13:45:26 -0700614 Execute this task. Attempt to schedule the associated suite.
615 Return True if this task should be kept around, False if it
616 should be destroyed. This allows for one-shot Tasks.
Chris Masonefad911a2012-03-29 12:30:26 -0700617
618 @param scheduler: an instance of DedupingScheduler, as defined in
619 deduping_scheduler.py
Chris Masone05b19442012-04-17 13:37:55 -0700620 @param branch_builds: a dict mapping branch name to the build(s) to
Chris Masone96f16632012-04-04 18:36:03 -0700621 install for that branch, e.g.
Chris Masone05b19442012-04-17 13:37:55 -0700622 {'R18': ['x86-alex-release/R18-1655.0.0'],
623 'R19': ['x86-alex-release/R19-2077.0.0']}
Chris Masone96f16632012-04-04 18:36:03 -0700624 @param board: the board against which to run self._suite.
Chris Masonefad911a2012-03-29 12:30:26 -0700625 @param force: Always schedule the suite.
Dan Shia25e0d42015-07-23 15:00:04 -0700626 @param mv: an instance of manifest_versions.ManifestVersions.
627
Chris Masone013859b2012-04-01 13:45:26 -0700628 @return True if the task should be kept, False if not
Dan Shia25e0d42015-07-23 15:00:04 -0700629
Chris Masonefad911a2012-03-29 12:30:26 -0700630 """
Chris Masonea3a38172012-05-14 15:19:56 -0700631 logging.info('Running %s on %s', self._name, board)
Dan Shia25e0d42015-07-23 15:00:04 -0700632 is_firmware_build = 'firmware' in self.branch_specs
633 # firmware_rw_build is only needed if firmware_rw_build_spec is given.
634 firmware_rw_build = None
635 try:
636 if is_firmware_build:
637 # When build specified in branch_specs is a firmware build,
638 # we need a ChromeOS build to test with the firmware build.
639 cros_build = self._GetCrOSBuild(mv, board)
640 elif self.firmware_rw_build_spec:
641 # When firmware_rw_build_spec is specified, the test involves
642 # updating the firmware by firmware build specified in
643 # firmware_rw_build_spec.
644 firmware_rw_build = self._GetFirmwareRWBuild(
645 mv, board, self.firmware_rw_build_spec)
646 except manifest_versions.QueryException as e:
647 logging.error(e)
648 logging.error('Running %s on %s is failed. Failed to find build '
649 'required to run the suite.', self._name, board)
650 return False
651
Chris Masone96f16632012-04-04 18:36:03 -0700652 builds = []
Chris Masonefe5a5092012-04-11 18:29:07 -0700653 for branch, build in branch_builds.iteritems():
Chris Masonea3a38172012-05-14 15:19:56 -0700654 logging.info('Checking if %s fits spec %r',
655 branch, self.branch_specs)
Chris Masone96f16632012-04-04 18:36:03 -0700656 if self._FitsSpec(branch):
Dan Shibde10772015-08-18 10:15:58 -0700657 logging.debug('Build %s fits the spec.', build)
Chris Masone05b19442012-04-17 13:37:55 -0700658 builds.extend(build)
Chris Masone96f16632012-04-04 18:36:03 -0700659 for build in builds:
Chris Masone3fba86f2012-04-03 10:06:56 -0700660 try:
Dan Shia25e0d42015-07-23 15:00:04 -0700661 if is_firmware_build:
662 firmware_rw_build = build
663 else:
664 cros_build = build
665 if self.test_source == Builds.FIRMWARE_RW:
666 test_source_build = firmware_rw_build
667 elif self.test_source == Builds.CROS:
668 test_source_build = cros_build
669 else:
670 test_source_build = None
671 logging.debug('Schedule %s for builds %s.%s',
672 self._suite, builds,
673 (' Test source build is %s.' % test_source_build)
674 if test_source_build else None)
675
676 if not scheduler.ScheduleSuite(
677 self._suite, board, cros_build, self._pool, self._num,
678 self._priority, self._timeout, force,
679 file_bugs=self._file_bugs,
680 firmware_rw_build=firmware_rw_build,
Dan Shi29a16992015-09-22 11:29:58 -0700681 test_source_build=test_source_build,
682 job_retry=self._job_retry):
Chris Masone96f16632012-04-04 18:36:03 -0700683 logging.info('Skipping scheduling %s on %s for %s',
Dan Shia25e0d42015-07-23 15:00:04 -0700684 self._suite, builds, board)
Chris Masone3fba86f2012-04-03 10:06:56 -0700685 except deduping_scheduler.DedupingSchedulerException as e:
686 logging.error(e)
Chris Masonefad911a2012-03-29 12:30:26 -0700687 return True
688
689
Chris Masone013859b2012-04-01 13:45:26 -0700690class OneShotTask(Task):
691 """A Task that can be run only once. Can schedule itself."""
Chris Masonefad911a2012-03-29 12:30:26 -0700692
693
Dan Shia25e0d42015-07-23 15:00:04 -0700694 def Run(self, scheduler, branch_builds, board, force=False, mv=None):
Chris Masone013859b2012-04-01 13:45:26 -0700695 """Run this task. Returns False, indicating it should be destroyed.
Chris Masonefad911a2012-03-29 12:30:26 -0700696
Chris Masone013859b2012-04-01 13:45:26 -0700697 Run this task. Attempt to schedule the associated suite.
698 Return False, indicating to the caller that it should discard this task.
Chris Masonefad911a2012-03-29 12:30:26 -0700699
700 @param scheduler: an instance of DedupingScheduler, as defined in
701 deduping_scheduler.py
Chris Masone05b19442012-04-17 13:37:55 -0700702 @param branch_builds: a dict mapping branch name to the build(s) to
Chris Masone96f16632012-04-04 18:36:03 -0700703 install for that branch, e.g.
Chris Masone05b19442012-04-17 13:37:55 -0700704 {'R18': ['x86-alex-release/R18-1655.0.0'],
705 'R19': ['x86-alex-release/R19-2077.0.0']}
Chris Masone96f16632012-04-04 18:36:03 -0700706 @param board: the board against which to run self._suite.
Chris Masonefad911a2012-03-29 12:30:26 -0700707 @param force: Always schedule the suite.
Dan Shia25e0d42015-07-23 15:00:04 -0700708 @param mv: an instance of manifest_versions.ManifestVersions.
709
Chris Masonefad911a2012-03-29 12:30:26 -0700710 @return False
Dan Shia25e0d42015-07-23 15:00:04 -0700711
Chris Masonefad911a2012-03-29 12:30:26 -0700712 """
Dan Shia25e0d42015-07-23 15:00:04 -0700713 super(OneShotTask, self).Run(scheduler, branch_builds, board, force,
714 mv)
Chris Masonefad911a2012-03-29 12:30:26 -0700715 return False