blob: cfbe789b08d008e1a4fd696b4745f755bd7be0e3 [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',
Dan Shice1f20a2016-01-25 17:27:40 -0800161 'hour', 'day'])
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.')
Dan Shice1f20a2016-01-25 17:27:40 -0800211
212 try:
213 day = config.getint(section, 'day')
214 except ValueError as e:
215 raise MalformedConfigEntry("Ill-specified 'day': %r" % e)
216 if day is not None and (day < 0 or day > 6):
217 raise MalformedConfigEntry(
218 '`day` must be an integer between 0 and 6, where 0 is for '
219 'Monday and 6 is for Sunday.')
220 if day is not None and keyword != 'weekly':
221 raise MalformedConfigEntry(
222 '`day` is the trigger of the day of a week, that can only '
223 'apply to weekly events.')
224
Chris Masone96f16632012-04-04 18:36:03 -0700225 specs = []
226 if branches:
227 specs = re.split('\s*,\s*', branches)
228 Task.CheckBranchSpecs(specs)
Alex Millerc7bcf8b2013-09-07 20:13:20 -0700229 return keyword, Task(section, suite, specs, pool, num, boards,
Prashanth B6de2bde2014-03-25 18:45:02 -0700230 priority, timeout,
Dan Shia25e0d42015-07-23 15:00:04 -0700231 file_bugs=file_bugs if file_bugs else False,
232 cros_build_spec=cros_build_spec,
233 firmware_rw_build_spec=firmware_rw_build_spec,
Dan Shi9f256d92016-01-22 00:09:25 -0800234 test_source=test_source, job_retry=job_retry,
Dan Shice1f20a2016-01-25 17:27:40 -0800235 hour=hour, day=day)
Chris Masone96f16632012-04-04 18:36:03 -0700236
237
238 @staticmethod
239 def CheckBranchSpecs(branch_specs):
240 """Make sure entries in the list branch_specs are correctly formed.
241
Chris Masone67f06d62012-04-12 15:16:56 -0700242 We accept any of BARE_BRANCHES in |branch_specs|, as
Dan Shiaceb91d2013-02-20 12:41:28 -0800243 well as _one_ string of the form '>=RXX' or '==RXX', where 'RXX' is a
Chris Masone96f16632012-04-04 18:36:03 -0700244 CrOS milestone number.
245
246 @param branch_specs: an iterable of branch specifiers.
247 @raise MalformedConfigEntry if there's a problem parsing |branch_specs|.
248 """
249 have_seen_numeric_constraint = False
250 for branch in branch_specs:
Chris Masone67f06d62012-04-12 15:16:56 -0700251 if branch in BARE_BRANCHES:
Chris Masone96f16632012-04-04 18:36:03 -0700252 continue
Prashanth Balasubramanianf571aa62014-10-13 18:09:44 -0700253 if not have_seen_numeric_constraint:
254 #TODO(beeps): Why was <= dropped on the floor?
255 if branch.startswith('>=R') or branch.startswith('==R'):
256 have_seen_numeric_constraint = True
257 elif 'tot' in branch:
258 TotMilestoneManager().ConvertTotSpec(
259 branch[branch.index('tot'):])
260 have_seen_numeric_constraint = True
Chris Masone96f16632012-04-04 18:36:03 -0700261 continue
Chris Masone97cf0a72012-05-16 09:55:52 -0700262 raise MalformedConfigEntry("%s isn't a valid branch spec." % branch)
Chris Masone96f16632012-04-04 18:36:03 -0700263
264
Alex Millerf2b57442013-09-07 18:40:02 -0700265 def __init__(self, name, suite, branch_specs, pool=None, num=None,
Dan Shia25e0d42015-07-23 15:00:04 -0700266 boards=None, priority=None, timeout=None, file_bugs=False,
267 cros_build_spec=None, firmware_rw_build_spec=None,
Dan Shice1f20a2016-01-25 17:27:40 -0800268 test_source=None, job_retry=False, hour=None, day=None):
Chris Masonefad911a2012-03-29 12:30:26 -0700269 """Constructor
270
Chris Masone96f16632012-04-04 18:36:03 -0700271 Given an iterable in |branch_specs|, pre-vetted using CheckBranchSpecs,
272 we'll store them such that _FitsSpec() can be used to check whether a
273 given branch 'fits' with the specifications passed in here.
274 For example, given branch_specs = ['factory', '>=R18'], we'd set things
275 up so that _FitsSpec() would return True for 'factory', or 'RXX'
Dan Shiaceb91d2013-02-20 12:41:28 -0800276 where XX is a number >= 18. Same check is done for branch_specs = [
277 'factory', '==R18'], which limit the test to only one specific branch.
Chris Masone96f16632012-04-04 18:36:03 -0700278
279 Given branch_specs = ['factory', 'firmware'], _FitsSpec()
280 would pass only those two specific strings.
281
282 Example usage:
Chris Masonecc4631d2012-04-20 12:06:39 -0700283 t = Task('Name', 'suite', ['factory', '>=R18'])
Chris Masone96f16632012-04-04 18:36:03 -0700284 t._FitsSpec('factory') # True
285 t._FitsSpec('R19') # True
286 t._FitsSpec('R17') # False
287 t._FitsSpec('firmware') # False
288 t._FitsSpec('goober') # False
289
Dan Shiaceb91d2013-02-20 12:41:28 -0800290 t = Task('Name', 'suite', ['factory', '==R18'])
291 t._FitsSpec('R19') # False, branch does not equal to 18
292 t._FitsSpec('R18') # True
293 t._FitsSpec('R17') # False
294
Dan Shia25e0d42015-07-23 15:00:04 -0700295 cros_build_spec and firmware_rw_build_spec are set for tests require
296 firmware update on the dut. Only one of them can be set.
297 For example:
298 branch_specs: ==tot
299 firmware_rw_build_spec: firmware
300 test_source: cros
301 This will run test using latest build on firmware branch, and the latest
302 ChromeOS build on ToT. The test source build is ChromeOS build.
303
304 branch_specs: firmware
305 cros_build_spec: ==tot-1
306 test_source: firmware_rw
307 This will run test using latest build on firmware branch, and the latest
308 ChromeOS build on dev channel (ToT-1). The test source build is the
309 firmware RW build.
310
Dan Shi59562a82016-01-06 16:05:31 -0800311 branch_specs: ==tot
312 firmware_rw_build_spec: cros
313 test_source: cros
314 This will run test using latest ChromeOS and firmware RW build on ToT.
315 ChromeOS build on ToT. The test source build is ChromeOS build.
316
Chris Masone3eeaf0a2012-08-09 14:07:27 -0700317 @param name: name of this task, e.g. 'NightlyPower'
Chris Masonefad911a2012-03-29 12:30:26 -0700318 @param suite: the name of the suite to run, e.g. 'bvt'
Chris Masone96f16632012-04-04 18:36:03 -0700319 @param branch_specs: a pre-vetted iterable of branch specifiers,
320 e.g. ['>=R18', 'factory']
Chris Masonefad911a2012-03-29 12:30:26 -0700321 @param pool: the pool of machines to use for scheduling purposes.
322 Default: None
Chris Masone3eeaf0a2012-08-09 14:07:27 -0700323 @param num: the number of devices across which to shard the test suite.
Aviv Keshetd83ef442013-01-16 16:19:35 -0800324 Type: integer or None
Chris Masone3eeaf0a2012-08-09 14:07:27 -0700325 Default: None
Dan Shi9f256d92016-01-22 00:09:25 -0800326 @param boards: A comma separated list of boards to run this task on.
Alex Millerf2b57442013-09-07 18:40:02 -0700327 Default: Run on all boards.
Alex Millerc7bcf8b2013-09-07 20:13:20 -0700328 @param priority: The string name of a priority from
329 client.common_lib.priorities.Priority.
330 @param timeout: The max lifetime of the suite in hours.
Prashanth B6de2bde2014-03-25 18:45:02 -0700331 @param file_bugs: True if bug filing is desired for the suite created
332 for this task.
Dan Shia25e0d42015-07-23 15:00:04 -0700333 @param cros_build_spec: Spec used to determine the ChromeOS build to
334 test with a firmware build, e.g., tot, R41 etc.
335 @param firmware_rw_build_spec: Spec used to determine the firmware build
336 test with a ChromeOS build.
337 @param test_source: The source of test code when firmware will be
338 updated in the test. The value can be `firmware_rw`
339 or `cros`.
Dan Shi29a16992015-09-22 11:29:58 -0700340 @param job_retry: Set to True to enable job-level retry. Default is
341 False.
Dan Shi9f256d92016-01-22 00:09:25 -0800342 @param hour: An integer specifying the hour that a nightly run should
343 be triggered, default is set to 21.
Dan Shice1f20a2016-01-25 17:27:40 -0800344 @param day: An integer specifying the day of a week that a weekly run
345 should be triggered, default is set to 5, which is Saturday.
Chris Masonefad911a2012-03-29 12:30:26 -0700346 """
Chris Masonecc4631d2012-04-20 12:06:39 -0700347 self._name = name
Chris Masonefad911a2012-03-29 12:30:26 -0700348 self._suite = suite
Chris Masone96f16632012-04-04 18:36:03 -0700349 self._branch_specs = branch_specs
Chris Masonefad911a2012-03-29 12:30:26 -0700350 self._pool = pool
Aviv Keshetd83ef442013-01-16 16:19:35 -0800351 self._num = num
Alex Millerc7bcf8b2013-09-07 20:13:20 -0700352 self._priority = priority
353 self._timeout = timeout
Prashanth B6de2bde2014-03-25 18:45:02 -0700354 self._file_bugs = file_bugs
Dan Shia25e0d42015-07-23 15:00:04 -0700355 self._cros_build_spec = cros_build_spec
356 self._firmware_rw_build_spec = firmware_rw_build_spec
357 self._test_source = test_source
Dan Shi29a16992015-09-22 11:29:58 -0700358 self._job_retry = job_retry
Dan Shi9f256d92016-01-22 00:09:25 -0800359 self.hour = hour
Dan Shice1f20a2016-01-25 17:27:40 -0800360 self.day = day
Dan Shia25e0d42015-07-23 15:00:04 -0700361
362 if ((self._firmware_rw_build_spec or cros_build_spec) and
363 not self.test_source in [Builds.FIRMWARE_RW, Builds.CROS]):
364 raise MalformedConfigEntry(
365 'You must specify the build for test source. It can only '
366 'be `firmware_rw` or `cros`.')
367 if self._firmware_rw_build_spec and cros_build_spec:
368 raise MalformedConfigEntry(
369 'You cannot specify both firmware_rw_build_spec and '
370 'cros_build_spec. firmware_rw_build_spec is used to specify'
371 ' a firmware build when the suite requires firmware to be '
Dan Shi59562a82016-01-06 16:05:31 -0800372 'updated in the dut, its value can only be `firmware` or '
373 '`cros`. cros_build_spec is used to specify a ChromeOS '
374 'build when build_specs is set to firmware.')
Dan Shia25e0d42015-07-23 15:00:04 -0700375 if (self._firmware_rw_build_spec and
Dan Shi59562a82016-01-06 16:05:31 -0800376 self._firmware_rw_build_spec not in ['firmware', 'cros']):
Dan Shia25e0d42015-07-23 15:00:04 -0700377 raise MalformedConfigEntry(
Dan Shi59562a82016-01-06 16:05:31 -0800378 'firmware_rw_build_spec can only be empty, firmware or '
379 'cros. It does not support other build type yet.')
Chris Masone96f16632012-04-04 18:36:03 -0700380
381 self._bare_branches = []
Dan Shiaceb91d2013-02-20 12:41:28 -0800382 self._version_equal_constraint = False
Dan Shibde10772015-08-18 10:15:58 -0700383 self._version_gte_constraint = False
384 self._version_lte_constraint = False
Chris Masone67f06d62012-04-12 15:16:56 -0700385 if not branch_specs:
386 # Any milestone is OK.
387 self._numeric_constraint = version.LooseVersion('0')
388 else:
389 self._numeric_constraint = None
390 for spec in branch_specs:
Prashanth Balasubramanianf571aa62014-10-13 18:09:44 -0700391 if 'tot' in spec.lower():
392 tot_str = spec[spec.index('tot'):]
393 spec = spec.replace(
394 tot_str, TotMilestoneManager().ConvertTotSpec(
Dan Shibde10772015-08-18 10:15:58 -0700395 tot_str))
Chris Masone67f06d62012-04-12 15:16:56 -0700396 if spec.startswith('>='):
397 self._numeric_constraint = version.LooseVersion(
Dan Shibde10772015-08-18 10:15:58 -0700398 spec.lstrip('>=R'))
399 self._version_gte_constraint = True
400 elif spec.startswith('<='):
401 self._numeric_constraint = version.LooseVersion(
402 spec.lstrip('<=R'))
403 self._version_lte_constraint = True
Dan Shiaceb91d2013-02-20 12:41:28 -0800404 elif spec.startswith('=='):
405 self._version_equal_constraint = True
406 self._numeric_constraint = version.LooseVersion(
Dan Shibde10772015-08-18 10:15:58 -0700407 spec.lstrip('==R'))
Chris Masone67f06d62012-04-12 15:16:56 -0700408 else:
409 self._bare_branches.append(spec)
Alex Millerf2b57442013-09-07 18:40:02 -0700410
Aviv Keshet52c7a212015-12-07 15:27:22 -0800411 # Since we expect __hash__() and other comparator methods to be used
Chris Masonefad911a2012-03-29 12:30:26 -0700412 # frequently by set operations, and they use str() a lot, pre-compute
413 # the string representation of this object.
Aviv Keshet8ce3c982013-01-24 09:23:13 -0800414 if num is None:
415 numStr = '[Default num]'
416 else:
417 numStr = '%d' % num
Alex Millerf2b57442013-09-07 18:40:02 -0700418
419 if boards is None:
420 self._boards = set()
421 boardsStr = '[All boards]'
422 else:
423 self._boards = set([x.strip() for x in boards.split(',')])
424 boardsStr = boards
425
Prashanth B6de2bde2014-03-25 18:45:02 -0700426 self._str = ('%s: %s on %s with pool %s, boards [%s], file_bugs = %s '
427 'across %s machines.' % (self.__class__.__name__,
428 suite, branch_specs, pool, boardsStr, self._file_bugs,
429 numStr))
Chris Masone96f16632012-04-04 18:36:03 -0700430
431
432 def _FitsSpec(self, branch):
433 """Checks if a branch is deemed OK by this instance's branch specs.
434
435 When called on a branch name, will return whether that branch
Dan Shiaceb91d2013-02-20 12:41:28 -0800436 'fits' the specifications stored in self._bare_branches,
Dan Shibde10772015-08-18 10:15:58 -0700437 self._numeric_constraint, self._version_equal_constraint,
438 self._version_gte_constraint and self._version_lte_constraint.
Chris Masone96f16632012-04-04 18:36:03 -0700439
440 @param branch: the branch to check.
441 @return True if b 'fits' with stored specs, False otherwise.
442 """
Chris Masone657e1552012-05-30 17:06:20 -0700443 if branch in BARE_BRANCHES:
444 return branch in self._bare_branches
Dan Shiaceb91d2013-02-20 12:41:28 -0800445 if self._numeric_constraint:
446 if self._version_equal_constraint:
447 return version.LooseVersion(branch) == self._numeric_constraint
Dan Shibde10772015-08-18 10:15:58 -0700448 elif self._version_gte_constraint:
449 return version.LooseVersion(branch) >= self._numeric_constraint
450 elif self._version_lte_constraint:
451 return version.LooseVersion(branch) <= self._numeric_constraint
Dan Shiaceb91d2013-02-20 12:41:28 -0800452 else:
Dan Shibde10772015-08-18 10:15:58 -0700453 # Default to great or equal constraint.
Dan Shiaceb91d2013-02-20 12:41:28 -0800454 return version.LooseVersion(branch) >= self._numeric_constraint
455 else:
456 return False
Chris Masonefad911a2012-03-29 12:30:26 -0700457
458
459 @property
Alex Miller9979b5a2012-11-01 17:36:12 -0700460 def name(self):
Dan Shiaceb91d2013-02-20 12:41:28 -0800461 """Name of this task, e.g. 'NightlyPower'."""
Alex Miller9979b5a2012-11-01 17:36:12 -0700462 return self._name
463
464
465 @property
Chris Masonefad911a2012-03-29 12:30:26 -0700466 def suite(self):
Dan Shiaceb91d2013-02-20 12:41:28 -0800467 """Name of the suite to run, e.g. 'bvt'."""
Chris Masonefad911a2012-03-29 12:30:26 -0700468 return self._suite
469
470
471 @property
Chris Masone96f16632012-04-04 18:36:03 -0700472 def branch_specs(self):
Dan Shiaceb91d2013-02-20 12:41:28 -0800473 """a pre-vetted iterable of branch specifiers,
474 e.g. ['>=R18', 'factory']."""
Chris Masone96f16632012-04-04 18:36:03 -0700475 return self._branch_specs
Chris Masonefad911a2012-03-29 12:30:26 -0700476
477
478 @property
Chris Masone3fba86f2012-04-03 10:06:56 -0700479 def pool(self):
Dan Shiaceb91d2013-02-20 12:41:28 -0800480 """The pool of machines to use for scheduling purposes."""
Chris Masone3fba86f2012-04-03 10:06:56 -0700481 return self._pool
Chris Masonefad911a2012-03-29 12:30:26 -0700482
483
Alex Miller9979b5a2012-11-01 17:36:12 -0700484 @property
485 def num(self):
Dan Shiaceb91d2013-02-20 12:41:28 -0800486 """The number of devices across which to shard the test suite.
487 Type: integer or None"""
Alex Miller9979b5a2012-11-01 17:36:12 -0700488 return self._num
489
490
Alex Millerf2b57442013-09-07 18:40:02 -0700491 @property
492 def boards(self):
493 """The boards on which to run this suite.
494 Type: Iterable of strings"""
495 return self._boards
496
497
Alex Millerc7bcf8b2013-09-07 20:13:20 -0700498 @property
499 def priority(self):
500 """The priority of the suite"""
501 return self._priority
502
503
504 @property
505 def timeout(self):
506 """The maximum lifetime of the suite in hours."""
507 return self._timeout
508
509
Dan Shia25e0d42015-07-23 15:00:04 -0700510 @property
511 def cros_build_spec(self):
512 """The build spec of ChromeOS to test with a firmware build."""
513 return self._cros_build_spec
514
515
516 @property
517 def firmware_rw_build_spec(self):
518 """The build spec of firmware to test with a ChromeOS build."""
519 return self._firmware_rw_build_spec
520
521
522 @property
523 def test_source(self):
524 """Source of the test code, value can be `firmware_rw` or `cros`."""
525 return self._test_source
526
527
Chris Masonefad911a2012-03-29 12:30:26 -0700528 def __str__(self):
529 return self._str
530
531
Chris Masone3eeaf0a2012-08-09 14:07:27 -0700532 def __repr__(self):
533 return self._str
534
535
Chris Masonefad911a2012-03-29 12:30:26 -0700536 def __lt__(self, other):
537 return str(self) < str(other)
538
539
540 def __le__(self, other):
541 return str(self) <= str(other)
542
543
544 def __eq__(self, other):
545 return str(self) == str(other)
546
547
548 def __ne__(self, other):
549 return str(self) != str(other)
550
551
552 def __gt__(self, other):
553 return str(self) > str(other)
554
555
556 def __ge__(self, other):
557 return str(self) >= str(other)
558
559
560 def __hash__(self):
561 """Allows instances to be correctly deduped when used in a set."""
562 return hash(str(self))
563
564
Dan Shia25e0d42015-07-23 15:00:04 -0700565 def _GetCrOSBuild(self, mv, board):
566 """Get the ChromeOS build name to test with firmware build.
567
568 The ChromeOS build to be used is determined by `self.cros_build_spec`.
569 Its value can be:
570 tot: use the latest ToT build.
571 tot-x: use the latest build in x milestone before ToT.
572 Rxx: use the latest build on xx milestone.
573
574 @param board: the board against which to run self._suite.
575 @param mv: an instance of manifest_versions.ManifestVersions.
576
577 @return: The ChromeOS build name to test with firmware build.
578
579 """
580 if not self.cros_build_spec:
581 return None
582 if self.cros_build_spec.startswith('tot'):
583 milestone = TotMilestoneManager().ConvertTotSpec(
584 self.cros_build_spec)[1:]
585 elif self.cros_build_spec.startswith('R'):
586 milestone = self.cros_build_spec[1:]
587 milestone, latest_manifest = mv.GetLatestManifest(
588 board, 'release', milestone=milestone)
589 latest_build = base_event.BuildName(board, 'release', milestone,
590 latest_manifest)
591 logging.debug('Found latest build of %s for spec %s: %s',
592 board, self.cros_build_spec, latest_build)
593 return latest_build
594
595
596 def _GetFirmwareRWBuild(self, mv, board, build_type):
597 """Get the firmware rw build name to test with ChromeOS build.
598
599 The firmware rw build to be used is determined by
Dan Shi59562a82016-01-06 16:05:31 -0800600 `self.firmware_rw_build_spec`. Its value can be `firmware`, `cros` or
601 empty:
Dan Shia25e0d42015-07-23 15:00:04 -0700602 firmware: use the ToT build in firmware branch.
Dan Shi59562a82016-01-06 16:05:31 -0800603 cros: use the ToT build in release (ChromeOS) branch.
Dan Shia25e0d42015-07-23 15:00:04 -0700604
605 @param mv: an instance of manifest_versions.ManifestVersions.
606 @param board: the board against which to run self._suite.
Dan Shi59562a82016-01-06 16:05:31 -0800607 @param build_type: Build type of the firmware build, e.g., factory,
608 firmware or release.
Dan Shia25e0d42015-07-23 15:00:04 -0700609
610 @return: The firmware rw build name to test with ChromeOS build.
611
612 """
613 if not self.firmware_rw_build_spec:
614 return None
615 latest_milestone, latest_manifest = mv.GetLatestManifest(
616 board, build_type)
617 latest_build = base_event.BuildName(board, build_type, latest_milestone,
618 latest_manifest)
619 logging.debug('Found latest firmware build of %s for spec %s: %s',
620 board, self.firmware_rw_build_spec, latest_build)
621 return latest_build
622
623
Alex Miller7ce1b362012-07-10 09:24:10 -0700624 def AvailableHosts(self, scheduler, board):
625 """Query what hosts are able to run a test on a board and pool
626 combination.
Alex Miller511a9e32012-07-03 09:16:47 -0700627
628 @param scheduler: an instance of DedupingScheduler, as defined in
629 deduping_scheduler.py
630 @param board: the board against which one wants to run the test.
Alex Miller7ce1b362012-07-10 09:24:10 -0700631 @return The list of hosts meeting the board and pool requirements,
632 or None if no hosts were found."""
Alex Millerf2b57442013-09-07 18:40:02 -0700633 if self._boards and board not in self._boards:
634 return []
635
Alex Miller511a9e32012-07-03 09:16:47 -0700636 labels = [Labels.BOARD_PREFIX + board]
637 if self._pool:
Chris Masonecd214e02012-07-10 16:22:10 -0700638 labels.append(Labels.POOL_PREFIX + self._pool)
Alex Miller511a9e32012-07-03 09:16:47 -0700639
Prashanth B6de2bde2014-03-25 18:45:02 -0700640 return scheduler.CheckHostsExist(multiple_labels=labels)
Alex Miller511a9e32012-07-03 09:16:47 -0700641
642
Alex Millerd621cf22012-07-11 13:57:10 -0700643 def ShouldHaveAvailableHosts(self):
644 """As a sanity check, return true if we know for certain that
645 we should be able to schedule this test. If we claim this test
646 should be able to run, and it ends up not being scheduled, then
647 a warning will be reported.
648
649 @return True if this test should be able to run, False otherwise.
650 """
651 return self._pool == 'bvt'
652
653
Dan Shia25e0d42015-07-23 15:00:04 -0700654 def Run(self, scheduler, branch_builds, board, force=False, mv=None):
Chris Masone013859b2012-04-01 13:45:26 -0700655 """Run this task. Returns False if it should be destroyed.
Chris Masonefad911a2012-03-29 12:30:26 -0700656
Chris Masone013859b2012-04-01 13:45:26 -0700657 Execute this task. Attempt to schedule the associated suite.
658 Return True if this task should be kept around, False if it
659 should be destroyed. This allows for one-shot Tasks.
Chris Masonefad911a2012-03-29 12:30:26 -0700660
661 @param scheduler: an instance of DedupingScheduler, as defined in
662 deduping_scheduler.py
Chris Masone05b19442012-04-17 13:37:55 -0700663 @param branch_builds: a dict mapping branch name to the build(s) to
Chris Masone96f16632012-04-04 18:36:03 -0700664 install for that branch, e.g.
Chris Masone05b19442012-04-17 13:37:55 -0700665 {'R18': ['x86-alex-release/R18-1655.0.0'],
666 'R19': ['x86-alex-release/R19-2077.0.0']}
Chris Masone96f16632012-04-04 18:36:03 -0700667 @param board: the board against which to run self._suite.
Chris Masonefad911a2012-03-29 12:30:26 -0700668 @param force: Always schedule the suite.
Dan Shia25e0d42015-07-23 15:00:04 -0700669 @param mv: an instance of manifest_versions.ManifestVersions.
670
Chris Masone013859b2012-04-01 13:45:26 -0700671 @return True if the task should be kept, False if not
Dan Shia25e0d42015-07-23 15:00:04 -0700672
Chris Masonefad911a2012-03-29 12:30:26 -0700673 """
Chris Masonea3a38172012-05-14 15:19:56 -0700674 logging.info('Running %s on %s', self._name, board)
Dan Shia25e0d42015-07-23 15:00:04 -0700675 is_firmware_build = 'firmware' in self.branch_specs
676 # firmware_rw_build is only needed if firmware_rw_build_spec is given.
677 firmware_rw_build = None
678 try:
679 if is_firmware_build:
680 # When build specified in branch_specs is a firmware build,
681 # we need a ChromeOS build to test with the firmware build.
682 cros_build = self._GetCrOSBuild(mv, board)
683 elif self.firmware_rw_build_spec:
684 # When firmware_rw_build_spec is specified, the test involves
685 # updating the firmware by firmware build specified in
686 # firmware_rw_build_spec.
Dan Shi59562a82016-01-06 16:05:31 -0800687 if self.firmware_rw_build_spec == 'cros':
688 build_type = 'release'
689 else:
690 build_type = self.firmware_rw_build_spec
Dan Shia25e0d42015-07-23 15:00:04 -0700691 firmware_rw_build = self._GetFirmwareRWBuild(
Dan Shi59562a82016-01-06 16:05:31 -0800692 mv, board, build_type)
Dan Shia25e0d42015-07-23 15:00:04 -0700693 except manifest_versions.QueryException as e:
694 logging.error(e)
695 logging.error('Running %s on %s is failed. Failed to find build '
696 'required to run the suite.', self._name, board)
697 return False
698
Chris Masone96f16632012-04-04 18:36:03 -0700699 builds = []
Chris Masonefe5a5092012-04-11 18:29:07 -0700700 for branch, build in branch_builds.iteritems():
Chris Masonea3a38172012-05-14 15:19:56 -0700701 logging.info('Checking if %s fits spec %r',
702 branch, self.branch_specs)
Chris Masone96f16632012-04-04 18:36:03 -0700703 if self._FitsSpec(branch):
Dan Shibde10772015-08-18 10:15:58 -0700704 logging.debug('Build %s fits the spec.', build)
Chris Masone05b19442012-04-17 13:37:55 -0700705 builds.extend(build)
Chris Masone96f16632012-04-04 18:36:03 -0700706 for build in builds:
Chris Masone3fba86f2012-04-03 10:06:56 -0700707 try:
Dan Shia25e0d42015-07-23 15:00:04 -0700708 if is_firmware_build:
709 firmware_rw_build = build
710 else:
711 cros_build = build
712 if self.test_source == Builds.FIRMWARE_RW:
713 test_source_build = firmware_rw_build
714 elif self.test_source == Builds.CROS:
715 test_source_build = cros_build
716 else:
717 test_source_build = None
718 logging.debug('Schedule %s for builds %s.%s',
719 self._suite, builds,
720 (' Test source build is %s.' % test_source_build)
721 if test_source_build else None)
722
723 if not scheduler.ScheduleSuite(
724 self._suite, board, cros_build, self._pool, self._num,
725 self._priority, self._timeout, force,
726 file_bugs=self._file_bugs,
727 firmware_rw_build=firmware_rw_build,
Dan Shi29a16992015-09-22 11:29:58 -0700728 test_source_build=test_source_build,
729 job_retry=self._job_retry):
Chris Masone96f16632012-04-04 18:36:03 -0700730 logging.info('Skipping scheduling %s on %s for %s',
Dan Shia25e0d42015-07-23 15:00:04 -0700731 self._suite, builds, board)
Chris Masone3fba86f2012-04-03 10:06:56 -0700732 except deduping_scheduler.DedupingSchedulerException as e:
733 logging.error(e)
Chris Masonefad911a2012-03-29 12:30:26 -0700734 return True
735
736
Chris Masone013859b2012-04-01 13:45:26 -0700737class OneShotTask(Task):
738 """A Task that can be run only once. Can schedule itself."""
Chris Masonefad911a2012-03-29 12:30:26 -0700739
740
Dan Shia25e0d42015-07-23 15:00:04 -0700741 def Run(self, scheduler, branch_builds, board, force=False, mv=None):
Chris Masone013859b2012-04-01 13:45:26 -0700742 """Run this task. Returns False, indicating it should be destroyed.
Chris Masonefad911a2012-03-29 12:30:26 -0700743
Chris Masone013859b2012-04-01 13:45:26 -0700744 Run this task. Attempt to schedule the associated suite.
745 Return False, indicating to the caller that it should discard this task.
Chris Masonefad911a2012-03-29 12:30:26 -0700746
747 @param scheduler: an instance of DedupingScheduler, as defined in
748 deduping_scheduler.py
Chris Masone05b19442012-04-17 13:37:55 -0700749 @param branch_builds: a dict mapping branch name to the build(s) to
Chris Masone96f16632012-04-04 18:36:03 -0700750 install for that branch, e.g.
Chris Masone05b19442012-04-17 13:37:55 -0700751 {'R18': ['x86-alex-release/R18-1655.0.0'],
752 'R19': ['x86-alex-release/R19-2077.0.0']}
Chris Masone96f16632012-04-04 18:36:03 -0700753 @param board: the board against which to run self._suite.
Chris Masonefad911a2012-03-29 12:30:26 -0700754 @param force: Always schedule the suite.
Dan Shia25e0d42015-07-23 15:00:04 -0700755 @param mv: an instance of manifest_versions.ManifestVersions.
756
Chris Masonefad911a2012-03-29 12:30:26 -0700757 @return False
Dan Shia25e0d42015-07-23 15:00:04 -0700758
Chris Masonefad911a2012-03-29 12:30:26 -0700759 """
Dan Shia25e0d42015-07-23 15:00:04 -0700760 super(OneShotTask, self).Run(scheduler, branch_builds, board, force,
761 mv)
Chris Masonefad911a2012-03-29 12:30:26 -0700762 return False