blob: 2829204263bb91f38f5eb171556a6fc95669fb8a [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 Shi9f256d92016-01-22 00:09:25 -0800142 hour: integer of the hour to run, only applies to nightly. # Optional
Dan Shiaceb91d2013-02-20 12:41:28 -0800143 branch_specs: factory,firmware,>=R12 or ==R12 # Optional
Chris Masone96f16632012-04-04 18:36:03 -0700144 pool: pool_of_devices # Optional
Chris Masone3eeaf0a2012-08-09 14:07:27 -0700145 num: sharding_factor # int, Optional
Alex Millerf2b57442013-09-07 18:40:02 -0700146 boards: board1, board2 # comma seperated string, Optional
Chris Masone96f16632012-04-04 18:36:03 -0700147
Chris Masone67f06d62012-04-12 15:16:56 -0700148 By default, Tasks run on all release branches, not factory or firmware.
149
Chris Masone96f16632012-04-04 18:36:03 -0700150 @param config: a ForgivingConfigParser.
151 @param section: the section to parse into a Task.
152 @return keyword, Task object pair. One or both will be None on error.
153 @raise MalformedConfigEntry if there's a problem parsing |section|.
154 """
Alex Miller06695022012-07-18 09:31:36 -0700155 if not config.has_section(section):
156 raise MalformedConfigEntry('unknown section %s' % section)
157
Alex Millerf2b57442013-09-07 18:40:02 -0700158 allowed = set(['suite', 'run_on', 'branch_specs', 'pool', 'num',
Dan Shia25e0d42015-07-23 15:00:04 -0700159 'boards', 'file_bugs', 'cros_build_spec',
Dan Shi9f256d92016-01-22 00:09:25 -0800160 'firmware_rw_build_spec', 'test_source', 'job_retry',
161 'hour'])
Alex Millerbb535e22012-07-11 20:11:33 -0700162 # The parameter of union() is the keys under the section in the config
163 # The union merges this with the allowed set, so if any optional keys
164 # are omitted, then they're filled in. If any extra keys are present,
165 # then they will expand unioned set, causing it to fail the following
166 # comparison against the allowed set.
167 section_headers = allowed.union(dict(config.items(section)).keys())
168 if allowed != section_headers:
169 raise MalformedConfigEntry('unknown entries: %s' %
170 ", ".join(map(str, section_headers.difference(allowed))))
171
Chris Masone96f16632012-04-04 18:36:03 -0700172 keyword = config.getstring(section, 'run_on')
Dan Shi9f256d92016-01-22 00:09:25 -0800173 hour = config.getstring(section, 'hour')
Chris Masone96f16632012-04-04 18:36:03 -0700174 suite = config.getstring(section, 'suite')
175 branches = config.getstring(section, 'branch_specs')
176 pool = config.getstring(section, 'pool')
Alex Millerf2b57442013-09-07 18:40:02 -0700177 boards = config.getstring(section, 'boards')
Prashanth B6de2bde2014-03-25 18:45:02 -0700178 file_bugs = config.getboolean(section, 'file_bugs')
Dan Shia25e0d42015-07-23 15:00:04 -0700179 cros_build_spec = config.getstring(section, 'cros_build_spec')
180 firmware_rw_build_spec = config.getstring(
181 section, 'firmware_rw_build_spec')
182 test_source = config.getstring(section, 'test_source')
Dan Shi29a16992015-09-22 11:29:58 -0700183 job_retry = config.getboolean(section, 'job_retry')
Alex Millerc7bcf8b2013-09-07 20:13:20 -0700184 for klass in driver.Driver.EVENT_CLASSES:
185 if klass.KEYWORD == keyword:
186 priority = klass.PRIORITY
187 timeout = klass.TIMEOUT
188 break
189 else:
190 priority = None
191 timeout = None
Chris Masone3eeaf0a2012-08-09 14:07:27 -0700192 try:
193 num = config.getint(section, 'num')
194 except ValueError as e:
Dan Shi9f256d92016-01-22 00:09:25 -0800195 raise MalformedConfigEntry("Ill-specified 'num': %r" % e)
Chris Masone96f16632012-04-04 18:36:03 -0700196 if not keyword:
197 raise MalformedConfigEntry('No event to |run_on|.')
198 if not suite:
199 raise MalformedConfigEntry('No |suite|')
Dan Shi9f256d92016-01-22 00:09:25 -0800200 try:
201 hour = config.getint(section, 'hour')
202 except ValueError as e:
203 raise MalformedConfigEntry("Ill-specified 'hour': %r" % e)
204 if hour is not None and (hour < 0 or hour > 23):
205 raise MalformedConfigEntry(
206 '`hour` must be an integer between 0 and 23.')
207 if hour is not None and keyword != 'nightly':
208 raise MalformedConfigEntry(
209 '`hour` is the trigger time that can only apply to nightly '
210 'event.')
Chris Masone96f16632012-04-04 18:36:03 -0700211 specs = []
212 if branches:
213 specs = re.split('\s*,\s*', branches)
214 Task.CheckBranchSpecs(specs)
Alex Millerc7bcf8b2013-09-07 20:13:20 -0700215 return keyword, Task(section, suite, specs, pool, num, boards,
Prashanth B6de2bde2014-03-25 18:45:02 -0700216 priority, timeout,
Dan Shia25e0d42015-07-23 15:00:04 -0700217 file_bugs=file_bugs if file_bugs else False,
218 cros_build_spec=cros_build_spec,
219 firmware_rw_build_spec=firmware_rw_build_spec,
Dan Shi9f256d92016-01-22 00:09:25 -0800220 test_source=test_source, job_retry=job_retry,
221 hour=hour)
Chris Masone96f16632012-04-04 18:36:03 -0700222
223
224 @staticmethod
225 def CheckBranchSpecs(branch_specs):
226 """Make sure entries in the list branch_specs are correctly formed.
227
Chris Masone67f06d62012-04-12 15:16:56 -0700228 We accept any of BARE_BRANCHES in |branch_specs|, as
Dan Shiaceb91d2013-02-20 12:41:28 -0800229 well as _one_ string of the form '>=RXX' or '==RXX', where 'RXX' is a
Chris Masone96f16632012-04-04 18:36:03 -0700230 CrOS milestone number.
231
232 @param branch_specs: an iterable of branch specifiers.
233 @raise MalformedConfigEntry if there's a problem parsing |branch_specs|.
234 """
235 have_seen_numeric_constraint = False
236 for branch in branch_specs:
Chris Masone67f06d62012-04-12 15:16:56 -0700237 if branch in BARE_BRANCHES:
Chris Masone96f16632012-04-04 18:36:03 -0700238 continue
Prashanth Balasubramanianf571aa62014-10-13 18:09:44 -0700239 if not have_seen_numeric_constraint:
240 #TODO(beeps): Why was <= dropped on the floor?
241 if branch.startswith('>=R') or branch.startswith('==R'):
242 have_seen_numeric_constraint = True
243 elif 'tot' in branch:
244 TotMilestoneManager().ConvertTotSpec(
245 branch[branch.index('tot'):])
246 have_seen_numeric_constraint = True
Chris Masone96f16632012-04-04 18:36:03 -0700247 continue
Chris Masone97cf0a72012-05-16 09:55:52 -0700248 raise MalformedConfigEntry("%s isn't a valid branch spec." % branch)
Chris Masone96f16632012-04-04 18:36:03 -0700249
250
Alex Millerf2b57442013-09-07 18:40:02 -0700251 def __init__(self, name, suite, branch_specs, pool=None, num=None,
Dan Shia25e0d42015-07-23 15:00:04 -0700252 boards=None, priority=None, timeout=None, file_bugs=False,
253 cros_build_spec=None, firmware_rw_build_spec=None,
Dan Shi9f256d92016-01-22 00:09:25 -0800254 test_source=None, job_retry=False, hour=None):
Chris Masonefad911a2012-03-29 12:30:26 -0700255 """Constructor
256
Chris Masone96f16632012-04-04 18:36:03 -0700257 Given an iterable in |branch_specs|, pre-vetted using CheckBranchSpecs,
258 we'll store them such that _FitsSpec() can be used to check whether a
259 given branch 'fits' with the specifications passed in here.
260 For example, given branch_specs = ['factory', '>=R18'], we'd set things
261 up so that _FitsSpec() would return True for 'factory', or 'RXX'
Dan Shiaceb91d2013-02-20 12:41:28 -0800262 where XX is a number >= 18. Same check is done for branch_specs = [
263 'factory', '==R18'], which limit the test to only one specific branch.
Chris Masone96f16632012-04-04 18:36:03 -0700264
265 Given branch_specs = ['factory', 'firmware'], _FitsSpec()
266 would pass only those two specific strings.
267
268 Example usage:
Chris Masonecc4631d2012-04-20 12:06:39 -0700269 t = Task('Name', 'suite', ['factory', '>=R18'])
Chris Masone96f16632012-04-04 18:36:03 -0700270 t._FitsSpec('factory') # True
271 t._FitsSpec('R19') # True
272 t._FitsSpec('R17') # False
273 t._FitsSpec('firmware') # False
274 t._FitsSpec('goober') # False
275
Dan Shiaceb91d2013-02-20 12:41:28 -0800276 t = Task('Name', 'suite', ['factory', '==R18'])
277 t._FitsSpec('R19') # False, branch does not equal to 18
278 t._FitsSpec('R18') # True
279 t._FitsSpec('R17') # False
280
Dan Shia25e0d42015-07-23 15:00:04 -0700281 cros_build_spec and firmware_rw_build_spec are set for tests require
282 firmware update on the dut. Only one of them can be set.
283 For example:
284 branch_specs: ==tot
285 firmware_rw_build_spec: firmware
286 test_source: cros
287 This will run test using latest build on firmware branch, and the latest
288 ChromeOS build on ToT. The test source build is ChromeOS build.
289
290 branch_specs: firmware
291 cros_build_spec: ==tot-1
292 test_source: firmware_rw
293 This will run test using latest build on firmware branch, and the latest
294 ChromeOS build on dev channel (ToT-1). The test source build is the
295 firmware RW build.
296
Dan Shi59562a82016-01-06 16:05:31 -0800297 branch_specs: ==tot
298 firmware_rw_build_spec: cros
299 test_source: cros
300 This will run test using latest ChromeOS and firmware RW build on ToT.
301 ChromeOS build on ToT. The test source build is ChromeOS build.
302
Chris Masone3eeaf0a2012-08-09 14:07:27 -0700303 @param name: name of this task, e.g. 'NightlyPower'
Chris Masonefad911a2012-03-29 12:30:26 -0700304 @param suite: the name of the suite to run, e.g. 'bvt'
Chris Masone96f16632012-04-04 18:36:03 -0700305 @param branch_specs: a pre-vetted iterable of branch specifiers,
306 e.g. ['>=R18', 'factory']
Chris Masonefad911a2012-03-29 12:30:26 -0700307 @param pool: the pool of machines to use for scheduling purposes.
308 Default: None
Chris Masone3eeaf0a2012-08-09 14:07:27 -0700309 @param num: the number of devices across which to shard the test suite.
Aviv Keshetd83ef442013-01-16 16:19:35 -0800310 Type: integer or None
Chris Masone3eeaf0a2012-08-09 14:07:27 -0700311 Default: None
Dan Shi9f256d92016-01-22 00:09:25 -0800312 @param boards: A comma separated list of boards to run this task on.
Alex Millerf2b57442013-09-07 18:40:02 -0700313 Default: Run on all boards.
Alex Millerc7bcf8b2013-09-07 20:13:20 -0700314 @param priority: The string name of a priority from
315 client.common_lib.priorities.Priority.
316 @param timeout: The max lifetime of the suite in hours.
Prashanth B6de2bde2014-03-25 18:45:02 -0700317 @param file_bugs: True if bug filing is desired for the suite created
318 for this task.
Dan Shia25e0d42015-07-23 15:00:04 -0700319 @param cros_build_spec: Spec used to determine the ChromeOS build to
320 test with a firmware build, e.g., tot, R41 etc.
321 @param firmware_rw_build_spec: Spec used to determine the firmware build
322 test with a ChromeOS build.
323 @param test_source: The source of test code when firmware will be
324 updated in the test. The value can be `firmware_rw`
325 or `cros`.
Dan Shi29a16992015-09-22 11:29:58 -0700326 @param job_retry: Set to True to enable job-level retry. Default is
327 False.
Dan Shi9f256d92016-01-22 00:09:25 -0800328 @param hour: An integer specifying the hour that a nightly run should
329 be triggered, default is set to 21.
Chris Masonefad911a2012-03-29 12:30:26 -0700330 """
Chris Masonecc4631d2012-04-20 12:06:39 -0700331 self._name = name
Chris Masonefad911a2012-03-29 12:30:26 -0700332 self._suite = suite
Chris Masone96f16632012-04-04 18:36:03 -0700333 self._branch_specs = branch_specs
Chris Masonefad911a2012-03-29 12:30:26 -0700334 self._pool = pool
Aviv Keshetd83ef442013-01-16 16:19:35 -0800335 self._num = num
Alex Millerc7bcf8b2013-09-07 20:13:20 -0700336 self._priority = priority
337 self._timeout = timeout
Prashanth B6de2bde2014-03-25 18:45:02 -0700338 self._file_bugs = file_bugs
Dan Shia25e0d42015-07-23 15:00:04 -0700339 self._cros_build_spec = cros_build_spec
340 self._firmware_rw_build_spec = firmware_rw_build_spec
341 self._test_source = test_source
Dan Shi29a16992015-09-22 11:29:58 -0700342 self._job_retry = job_retry
Dan Shi9f256d92016-01-22 00:09:25 -0800343 self.hour = hour
Dan Shia25e0d42015-07-23 15:00:04 -0700344
345 if ((self._firmware_rw_build_spec or cros_build_spec) and
346 not self.test_source in [Builds.FIRMWARE_RW, Builds.CROS]):
347 raise MalformedConfigEntry(
348 'You must specify the build for test source. It can only '
349 'be `firmware_rw` or `cros`.')
350 if self._firmware_rw_build_spec and cros_build_spec:
351 raise MalformedConfigEntry(
352 'You cannot specify both firmware_rw_build_spec and '
353 'cros_build_spec. firmware_rw_build_spec is used to specify'
354 ' a firmware build when the suite requires firmware to be '
Dan Shi59562a82016-01-06 16:05:31 -0800355 'updated in the dut, its value can only be `firmware` or '
356 '`cros`. cros_build_spec is used to specify a ChromeOS '
357 'build when build_specs is set to firmware.')
Dan Shia25e0d42015-07-23 15:00:04 -0700358 if (self._firmware_rw_build_spec and
Dan Shi59562a82016-01-06 16:05:31 -0800359 self._firmware_rw_build_spec not in ['firmware', 'cros']):
Dan Shia25e0d42015-07-23 15:00:04 -0700360 raise MalformedConfigEntry(
Dan Shi59562a82016-01-06 16:05:31 -0800361 'firmware_rw_build_spec can only be empty, firmware or '
362 'cros. It does not support other build type yet.')
Chris Masone96f16632012-04-04 18:36:03 -0700363
364 self._bare_branches = []
Dan Shiaceb91d2013-02-20 12:41:28 -0800365 self._version_equal_constraint = False
Dan Shibde10772015-08-18 10:15:58 -0700366 self._version_gte_constraint = False
367 self._version_lte_constraint = False
Chris Masone67f06d62012-04-12 15:16:56 -0700368 if not branch_specs:
369 # Any milestone is OK.
370 self._numeric_constraint = version.LooseVersion('0')
371 else:
372 self._numeric_constraint = None
373 for spec in branch_specs:
Prashanth Balasubramanianf571aa62014-10-13 18:09:44 -0700374 if 'tot' in spec.lower():
375 tot_str = spec[spec.index('tot'):]
376 spec = spec.replace(
377 tot_str, TotMilestoneManager().ConvertTotSpec(
Dan Shibde10772015-08-18 10:15:58 -0700378 tot_str))
Chris Masone67f06d62012-04-12 15:16:56 -0700379 if spec.startswith('>='):
380 self._numeric_constraint = version.LooseVersion(
Dan Shibde10772015-08-18 10:15:58 -0700381 spec.lstrip('>=R'))
382 self._version_gte_constraint = True
383 elif spec.startswith('<='):
384 self._numeric_constraint = version.LooseVersion(
385 spec.lstrip('<=R'))
386 self._version_lte_constraint = True
Dan Shiaceb91d2013-02-20 12:41:28 -0800387 elif spec.startswith('=='):
388 self._version_equal_constraint = True
389 self._numeric_constraint = version.LooseVersion(
Dan Shibde10772015-08-18 10:15:58 -0700390 spec.lstrip('==R'))
Chris Masone67f06d62012-04-12 15:16:56 -0700391 else:
392 self._bare_branches.append(spec)
Alex Millerf2b57442013-09-07 18:40:02 -0700393
Aviv Keshet52c7a212015-12-07 15:27:22 -0800394 # Since we expect __hash__() and other comparator methods to be used
Chris Masonefad911a2012-03-29 12:30:26 -0700395 # frequently by set operations, and they use str() a lot, pre-compute
396 # the string representation of this object.
Aviv Keshet8ce3c982013-01-24 09:23:13 -0800397 if num is None:
398 numStr = '[Default num]'
399 else:
400 numStr = '%d' % num
Alex Millerf2b57442013-09-07 18:40:02 -0700401
402 if boards is None:
403 self._boards = set()
404 boardsStr = '[All boards]'
405 else:
406 self._boards = set([x.strip() for x in boards.split(',')])
407 boardsStr = boards
408
Prashanth B6de2bde2014-03-25 18:45:02 -0700409 self._str = ('%s: %s on %s with pool %s, boards [%s], file_bugs = %s '
410 'across %s machines.' % (self.__class__.__name__,
411 suite, branch_specs, pool, boardsStr, self._file_bugs,
412 numStr))
Chris Masone96f16632012-04-04 18:36:03 -0700413
414
415 def _FitsSpec(self, branch):
416 """Checks if a branch is deemed OK by this instance's branch specs.
417
418 When called on a branch name, will return whether that branch
Dan Shiaceb91d2013-02-20 12:41:28 -0800419 'fits' the specifications stored in self._bare_branches,
Dan Shibde10772015-08-18 10:15:58 -0700420 self._numeric_constraint, self._version_equal_constraint,
421 self._version_gte_constraint and self._version_lte_constraint.
Chris Masone96f16632012-04-04 18:36:03 -0700422
423 @param branch: the branch to check.
424 @return True if b 'fits' with stored specs, False otherwise.
425 """
Chris Masone657e1552012-05-30 17:06:20 -0700426 if branch in BARE_BRANCHES:
427 return branch in self._bare_branches
Dan Shiaceb91d2013-02-20 12:41:28 -0800428 if self._numeric_constraint:
429 if self._version_equal_constraint:
430 return version.LooseVersion(branch) == self._numeric_constraint
Dan Shibde10772015-08-18 10:15:58 -0700431 elif self._version_gte_constraint:
432 return version.LooseVersion(branch) >= self._numeric_constraint
433 elif self._version_lte_constraint:
434 return version.LooseVersion(branch) <= self._numeric_constraint
Dan Shiaceb91d2013-02-20 12:41:28 -0800435 else:
Dan Shibde10772015-08-18 10:15:58 -0700436 # Default to great or equal constraint.
Dan Shiaceb91d2013-02-20 12:41:28 -0800437 return version.LooseVersion(branch) >= self._numeric_constraint
438 else:
439 return False
Chris Masonefad911a2012-03-29 12:30:26 -0700440
441
442 @property
Alex Miller9979b5a2012-11-01 17:36:12 -0700443 def name(self):
Dan Shiaceb91d2013-02-20 12:41:28 -0800444 """Name of this task, e.g. 'NightlyPower'."""
Alex Miller9979b5a2012-11-01 17:36:12 -0700445 return self._name
446
447
448 @property
Chris Masonefad911a2012-03-29 12:30:26 -0700449 def suite(self):
Dan Shiaceb91d2013-02-20 12:41:28 -0800450 """Name of the suite to run, e.g. 'bvt'."""
Chris Masonefad911a2012-03-29 12:30:26 -0700451 return self._suite
452
453
454 @property
Chris Masone96f16632012-04-04 18:36:03 -0700455 def branch_specs(self):
Dan Shiaceb91d2013-02-20 12:41:28 -0800456 """a pre-vetted iterable of branch specifiers,
457 e.g. ['>=R18', 'factory']."""
Chris Masone96f16632012-04-04 18:36:03 -0700458 return self._branch_specs
Chris Masonefad911a2012-03-29 12:30:26 -0700459
460
461 @property
Chris Masone3fba86f2012-04-03 10:06:56 -0700462 def pool(self):
Dan Shiaceb91d2013-02-20 12:41:28 -0800463 """The pool of machines to use for scheduling purposes."""
Chris Masone3fba86f2012-04-03 10:06:56 -0700464 return self._pool
Chris Masonefad911a2012-03-29 12:30:26 -0700465
466
Alex Miller9979b5a2012-11-01 17:36:12 -0700467 @property
468 def num(self):
Dan Shiaceb91d2013-02-20 12:41:28 -0800469 """The number of devices across which to shard the test suite.
470 Type: integer or None"""
Alex Miller9979b5a2012-11-01 17:36:12 -0700471 return self._num
472
473
Alex Millerf2b57442013-09-07 18:40:02 -0700474 @property
475 def boards(self):
476 """The boards on which to run this suite.
477 Type: Iterable of strings"""
478 return self._boards
479
480
Alex Millerc7bcf8b2013-09-07 20:13:20 -0700481 @property
482 def priority(self):
483 """The priority of the suite"""
484 return self._priority
485
486
487 @property
488 def timeout(self):
489 """The maximum lifetime of the suite in hours."""
490 return self._timeout
491
492
Dan Shia25e0d42015-07-23 15:00:04 -0700493 @property
494 def cros_build_spec(self):
495 """The build spec of ChromeOS to test with a firmware build."""
496 return self._cros_build_spec
497
498
499 @property
500 def firmware_rw_build_spec(self):
501 """The build spec of firmware to test with a ChromeOS build."""
502 return self._firmware_rw_build_spec
503
504
505 @property
506 def test_source(self):
507 """Source of the test code, value can be `firmware_rw` or `cros`."""
508 return self._test_source
509
510
Chris Masonefad911a2012-03-29 12:30:26 -0700511 def __str__(self):
512 return self._str
513
514
Chris Masone3eeaf0a2012-08-09 14:07:27 -0700515 def __repr__(self):
516 return self._str
517
518
Chris Masonefad911a2012-03-29 12:30:26 -0700519 def __lt__(self, other):
520 return str(self) < str(other)
521
522
523 def __le__(self, other):
524 return str(self) <= str(other)
525
526
527 def __eq__(self, other):
528 return str(self) == str(other)
529
530
531 def __ne__(self, other):
532 return str(self) != str(other)
533
534
535 def __gt__(self, other):
536 return str(self) > str(other)
537
538
539 def __ge__(self, other):
540 return str(self) >= str(other)
541
542
543 def __hash__(self):
544 """Allows instances to be correctly deduped when used in a set."""
545 return hash(str(self))
546
547
Dan Shia25e0d42015-07-23 15:00:04 -0700548 def _GetCrOSBuild(self, mv, board):
549 """Get the ChromeOS build name to test with firmware build.
550
551 The ChromeOS build to be used is determined by `self.cros_build_spec`.
552 Its value can be:
553 tot: use the latest ToT build.
554 tot-x: use the latest build in x milestone before ToT.
555 Rxx: use the latest build on xx milestone.
556
557 @param board: the board against which to run self._suite.
558 @param mv: an instance of manifest_versions.ManifestVersions.
559
560 @return: The ChromeOS build name to test with firmware build.
561
562 """
563 if not self.cros_build_spec:
564 return None
565 if self.cros_build_spec.startswith('tot'):
566 milestone = TotMilestoneManager().ConvertTotSpec(
567 self.cros_build_spec)[1:]
568 elif self.cros_build_spec.startswith('R'):
569 milestone = self.cros_build_spec[1:]
570 milestone, latest_manifest = mv.GetLatestManifest(
571 board, 'release', milestone=milestone)
572 latest_build = base_event.BuildName(board, 'release', milestone,
573 latest_manifest)
574 logging.debug('Found latest build of %s for spec %s: %s',
575 board, self.cros_build_spec, latest_build)
576 return latest_build
577
578
579 def _GetFirmwareRWBuild(self, mv, board, build_type):
580 """Get the firmware rw build name to test with ChromeOS build.
581
582 The firmware rw build to be used is determined by
Dan Shi59562a82016-01-06 16:05:31 -0800583 `self.firmware_rw_build_spec`. Its value can be `firmware`, `cros` or
584 empty:
Dan Shia25e0d42015-07-23 15:00:04 -0700585 firmware: use the ToT build in firmware branch.
Dan Shi59562a82016-01-06 16:05:31 -0800586 cros: use the ToT build in release (ChromeOS) branch.
Dan Shia25e0d42015-07-23 15:00:04 -0700587
588 @param mv: an instance of manifest_versions.ManifestVersions.
589 @param board: the board against which to run self._suite.
Dan Shi59562a82016-01-06 16:05:31 -0800590 @param build_type: Build type of the firmware build, e.g., factory,
591 firmware or release.
Dan Shia25e0d42015-07-23 15:00:04 -0700592
593 @return: The firmware rw build name to test with ChromeOS build.
594
595 """
596 if not self.firmware_rw_build_spec:
597 return None
598 latest_milestone, latest_manifest = mv.GetLatestManifest(
599 board, build_type)
600 latest_build = base_event.BuildName(board, build_type, latest_milestone,
601 latest_manifest)
602 logging.debug('Found latest firmware build of %s for spec %s: %s',
603 board, self.firmware_rw_build_spec, latest_build)
604 return latest_build
605
606
Alex Miller7ce1b362012-07-10 09:24:10 -0700607 def AvailableHosts(self, scheduler, board):
608 """Query what hosts are able to run a test on a board and pool
609 combination.
Alex Miller511a9e32012-07-03 09:16:47 -0700610
611 @param scheduler: an instance of DedupingScheduler, as defined in
612 deduping_scheduler.py
613 @param board: the board against which one wants to run the test.
Alex Miller7ce1b362012-07-10 09:24:10 -0700614 @return The list of hosts meeting the board and pool requirements,
615 or None if no hosts were found."""
Alex Millerf2b57442013-09-07 18:40:02 -0700616 if self._boards and board not in self._boards:
617 return []
618
Alex Miller511a9e32012-07-03 09:16:47 -0700619 labels = [Labels.BOARD_PREFIX + board]
620 if self._pool:
Chris Masonecd214e02012-07-10 16:22:10 -0700621 labels.append(Labels.POOL_PREFIX + self._pool)
Alex Miller511a9e32012-07-03 09:16:47 -0700622
Prashanth B6de2bde2014-03-25 18:45:02 -0700623 return scheduler.CheckHostsExist(multiple_labels=labels)
Alex Miller511a9e32012-07-03 09:16:47 -0700624
625
Alex Millerd621cf22012-07-11 13:57:10 -0700626 def ShouldHaveAvailableHosts(self):
627 """As a sanity check, return true if we know for certain that
628 we should be able to schedule this test. If we claim this test
629 should be able to run, and it ends up not being scheduled, then
630 a warning will be reported.
631
632 @return True if this test should be able to run, False otherwise.
633 """
634 return self._pool == 'bvt'
635
636
Dan Shia25e0d42015-07-23 15:00:04 -0700637 def Run(self, scheduler, branch_builds, board, force=False, mv=None):
Chris Masone013859b2012-04-01 13:45:26 -0700638 """Run this task. Returns False if it should be destroyed.
Chris Masonefad911a2012-03-29 12:30:26 -0700639
Chris Masone013859b2012-04-01 13:45:26 -0700640 Execute this task. Attempt to schedule the associated suite.
641 Return True if this task should be kept around, False if it
642 should be destroyed. This allows for one-shot Tasks.
Chris Masonefad911a2012-03-29 12:30:26 -0700643
644 @param scheduler: an instance of DedupingScheduler, as defined in
645 deduping_scheduler.py
Chris Masone05b19442012-04-17 13:37:55 -0700646 @param branch_builds: a dict mapping branch name to the build(s) to
Chris Masone96f16632012-04-04 18:36:03 -0700647 install for that branch, e.g.
Chris Masone05b19442012-04-17 13:37:55 -0700648 {'R18': ['x86-alex-release/R18-1655.0.0'],
649 'R19': ['x86-alex-release/R19-2077.0.0']}
Chris Masone96f16632012-04-04 18:36:03 -0700650 @param board: the board against which to run self._suite.
Chris Masonefad911a2012-03-29 12:30:26 -0700651 @param force: Always schedule the suite.
Dan Shia25e0d42015-07-23 15:00:04 -0700652 @param mv: an instance of manifest_versions.ManifestVersions.
653
Chris Masone013859b2012-04-01 13:45:26 -0700654 @return True if the task should be kept, False if not
Dan Shia25e0d42015-07-23 15:00:04 -0700655
Chris Masonefad911a2012-03-29 12:30:26 -0700656 """
Chris Masonea3a38172012-05-14 15:19:56 -0700657 logging.info('Running %s on %s', self._name, board)
Dan Shia25e0d42015-07-23 15:00:04 -0700658 is_firmware_build = 'firmware' in self.branch_specs
659 # firmware_rw_build is only needed if firmware_rw_build_spec is given.
660 firmware_rw_build = None
661 try:
662 if is_firmware_build:
663 # When build specified in branch_specs is a firmware build,
664 # we need a ChromeOS build to test with the firmware build.
665 cros_build = self._GetCrOSBuild(mv, board)
666 elif self.firmware_rw_build_spec:
667 # When firmware_rw_build_spec is specified, the test involves
668 # updating the firmware by firmware build specified in
669 # firmware_rw_build_spec.
Dan Shi59562a82016-01-06 16:05:31 -0800670 if self.firmware_rw_build_spec == 'cros':
671 build_type = 'release'
672 else:
673 build_type = self.firmware_rw_build_spec
Dan Shia25e0d42015-07-23 15:00:04 -0700674 firmware_rw_build = self._GetFirmwareRWBuild(
Dan Shi59562a82016-01-06 16:05:31 -0800675 mv, board, build_type)
Dan Shia25e0d42015-07-23 15:00:04 -0700676 except manifest_versions.QueryException as e:
677 logging.error(e)
678 logging.error('Running %s on %s is failed. Failed to find build '
679 'required to run the suite.', self._name, board)
680 return False
681
Chris Masone96f16632012-04-04 18:36:03 -0700682 builds = []
Chris Masonefe5a5092012-04-11 18:29:07 -0700683 for branch, build in branch_builds.iteritems():
Chris Masonea3a38172012-05-14 15:19:56 -0700684 logging.info('Checking if %s fits spec %r',
685 branch, self.branch_specs)
Chris Masone96f16632012-04-04 18:36:03 -0700686 if self._FitsSpec(branch):
Dan Shibde10772015-08-18 10:15:58 -0700687 logging.debug('Build %s fits the spec.', build)
Chris Masone05b19442012-04-17 13:37:55 -0700688 builds.extend(build)
Chris Masone96f16632012-04-04 18:36:03 -0700689 for build in builds:
Chris Masone3fba86f2012-04-03 10:06:56 -0700690 try:
Dan Shia25e0d42015-07-23 15:00:04 -0700691 if is_firmware_build:
692 firmware_rw_build = build
693 else:
694 cros_build = build
695 if self.test_source == Builds.FIRMWARE_RW:
696 test_source_build = firmware_rw_build
697 elif self.test_source == Builds.CROS:
698 test_source_build = cros_build
699 else:
700 test_source_build = None
701 logging.debug('Schedule %s for builds %s.%s',
702 self._suite, builds,
703 (' Test source build is %s.' % test_source_build)
704 if test_source_build else None)
705
706 if not scheduler.ScheduleSuite(
707 self._suite, board, cros_build, self._pool, self._num,
708 self._priority, self._timeout, force,
709 file_bugs=self._file_bugs,
710 firmware_rw_build=firmware_rw_build,
Dan Shi29a16992015-09-22 11:29:58 -0700711 test_source_build=test_source_build,
712 job_retry=self._job_retry):
Chris Masone96f16632012-04-04 18:36:03 -0700713 logging.info('Skipping scheduling %s on %s for %s',
Dan Shia25e0d42015-07-23 15:00:04 -0700714 self._suite, builds, board)
Chris Masone3fba86f2012-04-03 10:06:56 -0700715 except deduping_scheduler.DedupingSchedulerException as e:
716 logging.error(e)
Chris Masonefad911a2012-03-29 12:30:26 -0700717 return True
718
719
Chris Masone013859b2012-04-01 13:45:26 -0700720class OneShotTask(Task):
721 """A Task that can be run only once. Can schedule itself."""
Chris Masonefad911a2012-03-29 12:30:26 -0700722
723
Dan Shia25e0d42015-07-23 15:00:04 -0700724 def Run(self, scheduler, branch_builds, board, force=False, mv=None):
Chris Masone013859b2012-04-01 13:45:26 -0700725 """Run this task. Returns False, indicating it should be destroyed.
Chris Masonefad911a2012-03-29 12:30:26 -0700726
Chris Masone013859b2012-04-01 13:45:26 -0700727 Run this task. Attempt to schedule the associated suite.
728 Return False, indicating to the caller that it should discard this task.
Chris Masonefad911a2012-03-29 12:30:26 -0700729
730 @param scheduler: an instance of DedupingScheduler, as defined in
731 deduping_scheduler.py
Chris Masone05b19442012-04-17 13:37:55 -0700732 @param branch_builds: a dict mapping branch name to the build(s) to
Chris Masone96f16632012-04-04 18:36:03 -0700733 install for that branch, e.g.
Chris Masone05b19442012-04-17 13:37:55 -0700734 {'R18': ['x86-alex-release/R18-1655.0.0'],
735 'R19': ['x86-alex-release/R19-2077.0.0']}
Chris Masone96f16632012-04-04 18:36:03 -0700736 @param board: the board against which to run self._suite.
Chris Masonefad911a2012-03-29 12:30:26 -0700737 @param force: Always schedule the suite.
Dan Shia25e0d42015-07-23 15:00:04 -0700738 @param mv: an instance of manifest_versions.ManifestVersions.
739
Chris Masonefad911a2012-03-29 12:30:26 -0700740 @return False
Dan Shia25e0d42015-07-23 15:00:04 -0700741
Chris Masonefad911a2012-03-29 12:30:26 -0700742 """
Dan Shia25e0d42015-07-23 15:00:04 -0700743 super(OneShotTask, self).Run(scheduler, branch_builds, board, force,
744 mv)
Chris Masonefad911a2012-03-29 12:30:26 -0700745 return False