blob: 3f9922be7b42a9490c2b869a625b467ff2a6bd73 [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
Chris Masone96f16632012-04-04 18:36:03 -07006import logging, re
Aviv Keshetd83ef442013-01-16 16:19:35 -08007import deduping_scheduler
Alex Millerc7bcf8b2013-09-07 20:13:20 -07008import driver
Chris Masone67f06d62012-04-12 15:16:56 -07009from distutils import version
Alex Miller511a9e32012-07-03 09:16:47 -070010from constants import Labels
Chris Masone96f16632012-04-04 18:36:03 -070011
12
13class MalformedConfigEntry(Exception):
14 """Raised to indicate a failure to parse a Task out of a config."""
15 pass
Chris Masonefad911a2012-03-29 12:30:26 -070016
17
Alex Miller0d003572013-03-18 11:51:30 -070018BARE_BRANCHES = ['factory', 'firmware']
Chris Masone67f06d62012-04-12 15:16:56 -070019
20
21def PickBranchName(type, milestone):
Dan Shiaceb91d2013-02-20 12:41:28 -080022 """Pick branch name. If type is among BARE_BRANCHES, return type,
23 otherwise, return milestone.
24
25 @param type: type of the branch, e.g., 'release', 'factory', or 'firmware'
26 @param milestone: CrOS milestone number
27 """
Chris Masone67f06d62012-04-12 15:16:56 -070028 if type in BARE_BRANCHES:
29 return type
30 return milestone
31
32
Chris Masone013859b2012-04-01 13:45:26 -070033class Task(object):
Chris Masonefad911a2012-03-29 12:30:26 -070034 """Represents an entry from the scheduler config. Can schedule itself.
35
36 Each entry from the scheduler config file maps one-to-one to a
Chris Masone013859b2012-04-01 13:45:26 -070037 Task. Each instance has enough info to schedule itself
Chris Masonefad911a2012-03-29 12:30:26 -070038 on-demand with the AFE.
39
40 This class also overrides __hash__() and all comparitor methods to enable
41 correct use in dicts, sets, etc.
42 """
43
Chris Masone96f16632012-04-04 18:36:03 -070044
45 @staticmethod
46 def CreateFromConfigSection(config, section):
47 """Create a Task from a section of a config file.
48
49 The section to parse should look like this:
50 [TaskName]
51 suite: suite_to_run # Required
52 run_on: event_on which to run # Required
Dan Shiaceb91d2013-02-20 12:41:28 -080053 branch_specs: factory,firmware,>=R12 or ==R12 # Optional
Chris Masone96f16632012-04-04 18:36:03 -070054 pool: pool_of_devices # Optional
Chris Masone3eeaf0a2012-08-09 14:07:27 -070055 num: sharding_factor # int, Optional
Alex Millerf2b57442013-09-07 18:40:02 -070056 boards: board1, board2 # comma seperated string, Optional
Chris Masone96f16632012-04-04 18:36:03 -070057
Chris Masone67f06d62012-04-12 15:16:56 -070058 By default, Tasks run on all release branches, not factory or firmware.
59
Chris Masone96f16632012-04-04 18:36:03 -070060 @param config: a ForgivingConfigParser.
61 @param section: the section to parse into a Task.
62 @return keyword, Task object pair. One or both will be None on error.
63 @raise MalformedConfigEntry if there's a problem parsing |section|.
64 """
Alex Miller06695022012-07-18 09:31:36 -070065 if not config.has_section(section):
66 raise MalformedConfigEntry('unknown section %s' % section)
67
Alex Millerf2b57442013-09-07 18:40:02 -070068 allowed = set(['suite', 'run_on', 'branch_specs', 'pool', 'num',
Prashanth B6de2bde2014-03-25 18:45:02 -070069 'boards', 'file_bugs'])
Alex Millerbb535e22012-07-11 20:11:33 -070070 # The parameter of union() is the keys under the section in the config
71 # The union merges this with the allowed set, so if any optional keys
72 # are omitted, then they're filled in. If any extra keys are present,
73 # then they will expand unioned set, causing it to fail the following
74 # comparison against the allowed set.
75 section_headers = allowed.union(dict(config.items(section)).keys())
76 if allowed != section_headers:
77 raise MalformedConfigEntry('unknown entries: %s' %
78 ", ".join(map(str, section_headers.difference(allowed))))
79
Chris Masone96f16632012-04-04 18:36:03 -070080 keyword = config.getstring(section, 'run_on')
81 suite = config.getstring(section, 'suite')
82 branches = config.getstring(section, 'branch_specs')
83 pool = config.getstring(section, 'pool')
Alex Millerf2b57442013-09-07 18:40:02 -070084 boards = config.getstring(section, 'boards')
Prashanth B6de2bde2014-03-25 18:45:02 -070085 file_bugs = config.getboolean(section, 'file_bugs')
Alex Millerc7bcf8b2013-09-07 20:13:20 -070086 for klass in driver.Driver.EVENT_CLASSES:
87 if klass.KEYWORD == keyword:
88 priority = klass.PRIORITY
89 timeout = klass.TIMEOUT
90 break
91 else:
92 priority = None
93 timeout = None
Chris Masone3eeaf0a2012-08-09 14:07:27 -070094 try:
95 num = config.getint(section, 'num')
96 except ValueError as e:
97 raise MalformedConfigEntry("Ill-specified 'num': %r" %e)
Chris Masone96f16632012-04-04 18:36:03 -070098 if not keyword:
99 raise MalformedConfigEntry('No event to |run_on|.')
100 if not suite:
101 raise MalformedConfigEntry('No |suite|')
102 specs = []
103 if branches:
104 specs = re.split('\s*,\s*', branches)
105 Task.CheckBranchSpecs(specs)
Alex Millerc7bcf8b2013-09-07 20:13:20 -0700106 return keyword, Task(section, suite, specs, pool, num, boards,
Prashanth B6de2bde2014-03-25 18:45:02 -0700107 priority, timeout,
108 file_bugs=file_bugs if file_bugs else False)
Chris Masone96f16632012-04-04 18:36:03 -0700109
110
111 @staticmethod
112 def CheckBranchSpecs(branch_specs):
113 """Make sure entries in the list branch_specs are correctly formed.
114
Chris Masone67f06d62012-04-12 15:16:56 -0700115 We accept any of BARE_BRANCHES in |branch_specs|, as
Dan Shiaceb91d2013-02-20 12:41:28 -0800116 well as _one_ string of the form '>=RXX' or '==RXX', where 'RXX' is a
Chris Masone96f16632012-04-04 18:36:03 -0700117 CrOS milestone number.
118
119 @param branch_specs: an iterable of branch specifiers.
120 @raise MalformedConfigEntry if there's a problem parsing |branch_specs|.
121 """
122 have_seen_numeric_constraint = False
123 for branch in branch_specs:
Chris Masone67f06d62012-04-12 15:16:56 -0700124 if branch in BARE_BRANCHES:
Chris Masone96f16632012-04-04 18:36:03 -0700125 continue
Dan Shiaceb91d2013-02-20 12:41:28 -0800126 if ((branch.startswith('>=R') or branch.startswith('==R')) and
127 not have_seen_numeric_constraint):
Chris Masone96f16632012-04-04 18:36:03 -0700128 have_seen_numeric_constraint = True
129 continue
Chris Masone97cf0a72012-05-16 09:55:52 -0700130 raise MalformedConfigEntry("%s isn't a valid branch spec." % branch)
Chris Masone96f16632012-04-04 18:36:03 -0700131
132
Alex Millerf2b57442013-09-07 18:40:02 -0700133 def __init__(self, name, suite, branch_specs, pool=None, num=None,
Prashanth B6de2bde2014-03-25 18:45:02 -0700134 boards=None, priority=None, timeout=None, file_bugs=False):
Chris Masonefad911a2012-03-29 12:30:26 -0700135 """Constructor
136
Chris Masone96f16632012-04-04 18:36:03 -0700137 Given an iterable in |branch_specs|, pre-vetted using CheckBranchSpecs,
138 we'll store them such that _FitsSpec() can be used to check whether a
139 given branch 'fits' with the specifications passed in here.
140 For example, given branch_specs = ['factory', '>=R18'], we'd set things
141 up so that _FitsSpec() would return True for 'factory', or 'RXX'
Dan Shiaceb91d2013-02-20 12:41:28 -0800142 where XX is a number >= 18. Same check is done for branch_specs = [
143 'factory', '==R18'], which limit the test to only one specific branch.
Chris Masone96f16632012-04-04 18:36:03 -0700144
145 Given branch_specs = ['factory', 'firmware'], _FitsSpec()
146 would pass only those two specific strings.
147
148 Example usage:
Chris Masonecc4631d2012-04-20 12:06:39 -0700149 t = Task('Name', 'suite', ['factory', '>=R18'])
Chris Masone96f16632012-04-04 18:36:03 -0700150 t._FitsSpec('factory') # True
151 t._FitsSpec('R19') # True
152 t._FitsSpec('R17') # False
153 t._FitsSpec('firmware') # False
154 t._FitsSpec('goober') # False
155
Dan Shiaceb91d2013-02-20 12:41:28 -0800156 t = Task('Name', 'suite', ['factory', '==R18'])
157 t._FitsSpec('R19') # False, branch does not equal to 18
158 t._FitsSpec('R18') # True
159 t._FitsSpec('R17') # False
160
Chris Masone3eeaf0a2012-08-09 14:07:27 -0700161 @param name: name of this task, e.g. 'NightlyPower'
Chris Masonefad911a2012-03-29 12:30:26 -0700162 @param suite: the name of the suite to run, e.g. 'bvt'
Chris Masone96f16632012-04-04 18:36:03 -0700163 @param branch_specs: a pre-vetted iterable of branch specifiers,
164 e.g. ['>=R18', 'factory']
Chris Masonefad911a2012-03-29 12:30:26 -0700165 @param pool: the pool of machines to use for scheduling purposes.
166 Default: None
Chris Masone3eeaf0a2012-08-09 14:07:27 -0700167 @param num: the number of devices across which to shard the test suite.
Aviv Keshetd83ef442013-01-16 16:19:35 -0800168 Type: integer or None
Chris Masone3eeaf0a2012-08-09 14:07:27 -0700169 Default: None
Alex Millerf2b57442013-09-07 18:40:02 -0700170 @param boards: A comma seperated list of boards to run this task on.
171 Default: Run on all boards.
Alex Millerc7bcf8b2013-09-07 20:13:20 -0700172 @param priority: The string name of a priority from
173 client.common_lib.priorities.Priority.
174 @param timeout: The max lifetime of the suite in hours.
Prashanth B6de2bde2014-03-25 18:45:02 -0700175 @param file_bugs: True if bug filing is desired for the suite created
176 for this task.
Chris Masonefad911a2012-03-29 12:30:26 -0700177 """
Chris Masonecc4631d2012-04-20 12:06:39 -0700178 self._name = name
Chris Masonefad911a2012-03-29 12:30:26 -0700179 self._suite = suite
Chris Masone96f16632012-04-04 18:36:03 -0700180 self._branch_specs = branch_specs
Chris Masonefad911a2012-03-29 12:30:26 -0700181 self._pool = pool
Aviv Keshetd83ef442013-01-16 16:19:35 -0800182 self._num = num
Alex Millerc7bcf8b2013-09-07 20:13:20 -0700183 self._priority = priority
184 self._timeout = timeout
Prashanth B6de2bde2014-03-25 18:45:02 -0700185 self._file_bugs = file_bugs
Chris Masone96f16632012-04-04 18:36:03 -0700186
187 self._bare_branches = []
Dan Shiaceb91d2013-02-20 12:41:28 -0800188 self._version_equal_constraint = False
Chris Masone67f06d62012-04-12 15:16:56 -0700189 if not branch_specs:
190 # Any milestone is OK.
191 self._numeric_constraint = version.LooseVersion('0')
192 else:
193 self._numeric_constraint = None
194 for spec in branch_specs:
195 if spec.startswith('>='):
196 self._numeric_constraint = version.LooseVersion(
197 spec.lstrip('>=R'))
Dan Shiaceb91d2013-02-20 12:41:28 -0800198 elif spec.startswith('=='):
199 self._version_equal_constraint = True
200 self._numeric_constraint = version.LooseVersion(
201 spec.lstrip('==R'))
Chris Masone67f06d62012-04-12 15:16:56 -0700202 else:
203 self._bare_branches.append(spec)
Alex Millerf2b57442013-09-07 18:40:02 -0700204
Chris Masonefad911a2012-03-29 12:30:26 -0700205 # Since we expect __hash__() and other comparitor methods to be used
206 # frequently by set operations, and they use str() a lot, pre-compute
207 # the string representation of this object.
Aviv Keshet8ce3c982013-01-24 09:23:13 -0800208 if num is None:
209 numStr = '[Default num]'
210 else:
211 numStr = '%d' % num
Alex Millerf2b57442013-09-07 18:40:02 -0700212
213 if boards is None:
214 self._boards = set()
215 boardsStr = '[All boards]'
216 else:
217 self._boards = set([x.strip() for x in boards.split(',')])
218 boardsStr = boards
219
Prashanth B6de2bde2014-03-25 18:45:02 -0700220 self._str = ('%s: %s on %s with pool %s, boards [%s], file_bugs = %s '
221 'across %s machines.' % (self.__class__.__name__,
222 suite, branch_specs, pool, boardsStr, self._file_bugs,
223 numStr))
Chris Masone96f16632012-04-04 18:36:03 -0700224
225
226 def _FitsSpec(self, branch):
227 """Checks if a branch is deemed OK by this instance's branch specs.
228
229 When called on a branch name, will return whether that branch
Dan Shiaceb91d2013-02-20 12:41:28 -0800230 'fits' the specifications stored in self._bare_branches,
231 self._numeric_constraint and self._version_equal_constraint.
Chris Masone96f16632012-04-04 18:36:03 -0700232
233 @param branch: the branch to check.
234 @return True if b 'fits' with stored specs, False otherwise.
235 """
Chris Masone657e1552012-05-30 17:06:20 -0700236 if branch in BARE_BRANCHES:
237 return branch in self._bare_branches
Dan Shiaceb91d2013-02-20 12:41:28 -0800238 if self._numeric_constraint:
239 if self._version_equal_constraint:
240 return version.LooseVersion(branch) == self._numeric_constraint
241 else:
242 return version.LooseVersion(branch) >= self._numeric_constraint
243 else:
244 return False
Chris Masonefad911a2012-03-29 12:30:26 -0700245
246
247 @property
Alex Miller9979b5a2012-11-01 17:36:12 -0700248 def name(self):
Dan Shiaceb91d2013-02-20 12:41:28 -0800249 """Name of this task, e.g. 'NightlyPower'."""
Alex Miller9979b5a2012-11-01 17:36:12 -0700250 return self._name
251
252
253 @property
Chris Masonefad911a2012-03-29 12:30:26 -0700254 def suite(self):
Dan Shiaceb91d2013-02-20 12:41:28 -0800255 """Name of the suite to run, e.g. 'bvt'."""
Chris Masonefad911a2012-03-29 12:30:26 -0700256 return self._suite
257
258
259 @property
Chris Masone96f16632012-04-04 18:36:03 -0700260 def branch_specs(self):
Dan Shiaceb91d2013-02-20 12:41:28 -0800261 """a pre-vetted iterable of branch specifiers,
262 e.g. ['>=R18', 'factory']."""
Chris Masone96f16632012-04-04 18:36:03 -0700263 return self._branch_specs
Chris Masonefad911a2012-03-29 12:30:26 -0700264
265
266 @property
Chris Masone3fba86f2012-04-03 10:06:56 -0700267 def pool(self):
Dan Shiaceb91d2013-02-20 12:41:28 -0800268 """The pool of machines to use for scheduling purposes."""
Chris Masone3fba86f2012-04-03 10:06:56 -0700269 return self._pool
Chris Masonefad911a2012-03-29 12:30:26 -0700270
271
Alex Miller9979b5a2012-11-01 17:36:12 -0700272 @property
273 def num(self):
Dan Shiaceb91d2013-02-20 12:41:28 -0800274 """The number of devices across which to shard the test suite.
275 Type: integer or None"""
Alex Miller9979b5a2012-11-01 17:36:12 -0700276 return self._num
277
278
Alex Millerf2b57442013-09-07 18:40:02 -0700279 @property
280 def boards(self):
281 """The boards on which to run this suite.
282 Type: Iterable of strings"""
283 return self._boards
284
285
Alex Millerc7bcf8b2013-09-07 20:13:20 -0700286 @property
287 def priority(self):
288 """The priority of the suite"""
289 return self._priority
290
291
292 @property
293 def timeout(self):
294 """The maximum lifetime of the suite in hours."""
295 return self._timeout
296
297
Chris Masonefad911a2012-03-29 12:30:26 -0700298 def __str__(self):
299 return self._str
300
301
Chris Masone3eeaf0a2012-08-09 14:07:27 -0700302 def __repr__(self):
303 return self._str
304
305
Chris Masonefad911a2012-03-29 12:30:26 -0700306 def __lt__(self, other):
307 return str(self) < str(other)
308
309
310 def __le__(self, other):
311 return str(self) <= str(other)
312
313
314 def __eq__(self, other):
315 return str(self) == str(other)
316
317
318 def __ne__(self, other):
319 return str(self) != str(other)
320
321
322 def __gt__(self, other):
323 return str(self) > str(other)
324
325
326 def __ge__(self, other):
327 return str(self) >= str(other)
328
329
330 def __hash__(self):
331 """Allows instances to be correctly deduped when used in a set."""
332 return hash(str(self))
333
334
Alex Miller7ce1b362012-07-10 09:24:10 -0700335 def AvailableHosts(self, scheduler, board):
336 """Query what hosts are able to run a test on a board and pool
337 combination.
Alex Miller511a9e32012-07-03 09:16:47 -0700338
339 @param scheduler: an instance of DedupingScheduler, as defined in
340 deduping_scheduler.py
341 @param board: the board against which one wants to run the test.
Alex Miller7ce1b362012-07-10 09:24:10 -0700342 @return The list of hosts meeting the board and pool requirements,
343 or None if no hosts were found."""
Alex Millerf2b57442013-09-07 18:40:02 -0700344 if self._boards and board not in self._boards:
345 return []
346
Alex Miller511a9e32012-07-03 09:16:47 -0700347 labels = [Labels.BOARD_PREFIX + board]
348 if self._pool:
Chris Masonecd214e02012-07-10 16:22:10 -0700349 labels.append(Labels.POOL_PREFIX + self._pool)
Alex Miller511a9e32012-07-03 09:16:47 -0700350
Prashanth B6de2bde2014-03-25 18:45:02 -0700351 return scheduler.CheckHostsExist(multiple_labels=labels)
Alex Miller511a9e32012-07-03 09:16:47 -0700352
353
Alex Millerd621cf22012-07-11 13:57:10 -0700354 def ShouldHaveAvailableHosts(self):
355 """As a sanity check, return true if we know for certain that
356 we should be able to schedule this test. If we claim this test
357 should be able to run, and it ends up not being scheduled, then
358 a warning will be reported.
359
360 @return True if this test should be able to run, False otherwise.
361 """
362 return self._pool == 'bvt'
363
364
Chris Masone96f16632012-04-04 18:36:03 -0700365 def Run(self, scheduler, branch_builds, board, force=False):
Chris Masone013859b2012-04-01 13:45:26 -0700366 """Run this task. Returns False if it should be destroyed.
Chris Masonefad911a2012-03-29 12:30:26 -0700367
Chris Masone013859b2012-04-01 13:45:26 -0700368 Execute this task. Attempt to schedule the associated suite.
369 Return True if this task should be kept around, False if it
370 should be destroyed. This allows for one-shot Tasks.
Chris Masonefad911a2012-03-29 12:30:26 -0700371
372 @param scheduler: an instance of DedupingScheduler, as defined in
373 deduping_scheduler.py
Chris Masone05b19442012-04-17 13:37:55 -0700374 @param branch_builds: a dict mapping branch name to the build(s) to
Chris Masone96f16632012-04-04 18:36:03 -0700375 install for that branch, e.g.
Chris Masone05b19442012-04-17 13:37:55 -0700376 {'R18': ['x86-alex-release/R18-1655.0.0'],
377 'R19': ['x86-alex-release/R19-2077.0.0']}
Chris Masone96f16632012-04-04 18:36:03 -0700378 @param board: the board against which to run self._suite.
Chris Masonefad911a2012-03-29 12:30:26 -0700379 @param force: Always schedule the suite.
Chris Masone013859b2012-04-01 13:45:26 -0700380 @return True if the task should be kept, False if not
Chris Masonefad911a2012-03-29 12:30:26 -0700381 """
Chris Masonea3a38172012-05-14 15:19:56 -0700382 logging.info('Running %s on %s', self._name, board)
Chris Masone96f16632012-04-04 18:36:03 -0700383 builds = []
Chris Masonefe5a5092012-04-11 18:29:07 -0700384 for branch, build in branch_builds.iteritems():
Chris Masonea3a38172012-05-14 15:19:56 -0700385 logging.info('Checking if %s fits spec %r',
386 branch, self.branch_specs)
Chris Masone96f16632012-04-04 18:36:03 -0700387 if self._FitsSpec(branch):
Chris Masone05b19442012-04-17 13:37:55 -0700388 builds.extend(build)
Chris Masone96f16632012-04-04 18:36:03 -0700389 for build in builds:
Chris Masone3fba86f2012-04-03 10:06:56 -0700390 try:
Chris Masone96f16632012-04-04 18:36:03 -0700391 if not scheduler.ScheduleSuite(self._suite, board, build,
Alex Millerc7bcf8b2013-09-07 20:13:20 -0700392 self._pool, self._num,
393 self._priority, self._timeout,
Prashanth B6de2bde2014-03-25 18:45:02 -0700394 force,
395 file_bugs=self._file_bugs):
Chris Masone96f16632012-04-04 18:36:03 -0700396 logging.info('Skipping scheduling %s on %s for %s',
397 self._suite, build, board)
Chris Masone3fba86f2012-04-03 10:06:56 -0700398 except deduping_scheduler.DedupingSchedulerException as e:
399 logging.error(e)
Chris Masonefad911a2012-03-29 12:30:26 -0700400 return True
401
402
Chris Masone013859b2012-04-01 13:45:26 -0700403class OneShotTask(Task):
404 """A Task that can be run only once. Can schedule itself."""
Chris Masonefad911a2012-03-29 12:30:26 -0700405
406
Chris Masone96f16632012-04-04 18:36:03 -0700407 def Run(self, scheduler, branch_builds, board, force=False):
Chris Masone013859b2012-04-01 13:45:26 -0700408 """Run this task. Returns False, indicating it should be destroyed.
Chris Masonefad911a2012-03-29 12:30:26 -0700409
Chris Masone013859b2012-04-01 13:45:26 -0700410 Run this task. Attempt to schedule the associated suite.
411 Return False, indicating to the caller that it should discard this task.
Chris Masonefad911a2012-03-29 12:30:26 -0700412
413 @param scheduler: an instance of DedupingScheduler, as defined in
414 deduping_scheduler.py
Chris Masone05b19442012-04-17 13:37:55 -0700415 @param branch_builds: a dict mapping branch name to the build(s) to
Chris Masone96f16632012-04-04 18:36:03 -0700416 install for that branch, e.g.
Chris Masone05b19442012-04-17 13:37:55 -0700417 {'R18': ['x86-alex-release/R18-1655.0.0'],
418 'R19': ['x86-alex-release/R19-2077.0.0']}
Chris Masone96f16632012-04-04 18:36:03 -0700419 @param board: the board against which to run self._suite.
Chris Masonefad911a2012-03-29 12:30:26 -0700420 @param force: Always schedule the suite.
421 @return False
422 """
Chris Masone96f16632012-04-04 18:36:03 -0700423 super(OneShotTask, self).Run(scheduler, branch_builds, board, force)
Chris Masonefad911a2012-03-29 12:30:26 -0700424 return False