blob: b0c24566d18b7f38143338bd9792bb24f143978d [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
Chris Masone67f06d62012-04-12 15:16:56 -07007import deduping_scheduler, forgiving_config_parser
8from distutils import version
Alex Miller511a9e32012-07-03 09:16:47 -07009from constants import Labels
Chris Masone96f16632012-04-04 18:36:03 -070010
11
12class MalformedConfigEntry(Exception):
13 """Raised to indicate a failure to parse a Task out of a config."""
14 pass
Chris Masonefad911a2012-03-29 12:30:26 -070015
16
Chris Masone67f06d62012-04-12 15:16:56 -070017BARE_BRANCHES = ['factory', 'firmware']
18
19
20def PickBranchName(type, milestone):
21 if type in BARE_BRANCHES:
22 return type
23 return milestone
24
25
Chris Masone013859b2012-04-01 13:45:26 -070026class Task(object):
Chris Masonefad911a2012-03-29 12:30:26 -070027 """Represents an entry from the scheduler config. Can schedule itself.
28
29 Each entry from the scheduler config file maps one-to-one to a
Chris Masone013859b2012-04-01 13:45:26 -070030 Task. Each instance has enough info to schedule itself
Chris Masonefad911a2012-03-29 12:30:26 -070031 on-demand with the AFE.
32
33 This class also overrides __hash__() and all comparitor methods to enable
34 correct use in dicts, sets, etc.
35 """
36
Chris Masone96f16632012-04-04 18:36:03 -070037
38 @staticmethod
39 def CreateFromConfigSection(config, section):
40 """Create a Task from a section of a config file.
41
42 The section to parse should look like this:
43 [TaskName]
44 suite: suite_to_run # Required
45 run_on: event_on which to run # Required
46 branch_specs: factory,firmware,>=R12 # Optional
47 pool: pool_of_devices # Optional
48
Chris Masone67f06d62012-04-12 15:16:56 -070049 By default, Tasks run on all release branches, not factory or firmware.
50
Chris Masone96f16632012-04-04 18:36:03 -070051 @param config: a ForgivingConfigParser.
52 @param section: the section to parse into a Task.
53 @return keyword, Task object pair. One or both will be None on error.
54 @raise MalformedConfigEntry if there's a problem parsing |section|.
55 """
Alex Miller06695022012-07-18 09:31:36 -070056 if not config.has_section(section):
57 raise MalformedConfigEntry('unknown section %s' % section)
58
Alex Millerbb535e22012-07-11 20:11:33 -070059 allowed = set(['suite', 'run_on', 'branch_specs', 'pool'])
60 # The parameter of union() is the keys under the section in the config
61 # The union merges this with the allowed set, so if any optional keys
62 # are omitted, then they're filled in. If any extra keys are present,
63 # then they will expand unioned set, causing it to fail the following
64 # comparison against the allowed set.
65 section_headers = allowed.union(dict(config.items(section)).keys())
66 if allowed != section_headers:
67 raise MalformedConfigEntry('unknown entries: %s' %
68 ", ".join(map(str, section_headers.difference(allowed))))
69
Chris Masone96f16632012-04-04 18:36:03 -070070 keyword = config.getstring(section, 'run_on')
71 suite = config.getstring(section, 'suite')
72 branches = config.getstring(section, 'branch_specs')
73 pool = config.getstring(section, 'pool')
74 if not keyword:
75 raise MalformedConfigEntry('No event to |run_on|.')
76 if not suite:
77 raise MalformedConfigEntry('No |suite|')
78 specs = []
79 if branches:
80 specs = re.split('\s*,\s*', branches)
81 Task.CheckBranchSpecs(specs)
Chris Masonecc4631d2012-04-20 12:06:39 -070082 return keyword, Task(section, suite, specs, pool)
Chris Masone96f16632012-04-04 18:36:03 -070083
84
85 @staticmethod
86 def CheckBranchSpecs(branch_specs):
87 """Make sure entries in the list branch_specs are correctly formed.
88
Chris Masone67f06d62012-04-12 15:16:56 -070089 We accept any of BARE_BRANCHES in |branch_specs|, as
Chris Masone96f16632012-04-04 18:36:03 -070090 well as _one_ string of the form '>=RXX', where 'RXX' is a
91 CrOS milestone number.
92
93 @param branch_specs: an iterable of branch specifiers.
94 @raise MalformedConfigEntry if there's a problem parsing |branch_specs|.
95 """
96 have_seen_numeric_constraint = False
97 for branch in branch_specs:
Chris Masone67f06d62012-04-12 15:16:56 -070098 if branch in BARE_BRANCHES:
Chris Masone96f16632012-04-04 18:36:03 -070099 continue
100 if branch.startswith('>=R') and not have_seen_numeric_constraint:
101 have_seen_numeric_constraint = True
102 continue
Chris Masone97cf0a72012-05-16 09:55:52 -0700103 raise MalformedConfigEntry("%s isn't a valid branch spec." % branch)
Chris Masone96f16632012-04-04 18:36:03 -0700104
105
Chris Masonecc4631d2012-04-20 12:06:39 -0700106 def __init__(self, name, suite, branch_specs, pool=None):
Chris Masonefad911a2012-03-29 12:30:26 -0700107 """Constructor
108
Chris Masone96f16632012-04-04 18:36:03 -0700109 Given an iterable in |branch_specs|, pre-vetted using CheckBranchSpecs,
110 we'll store them such that _FitsSpec() can be used to check whether a
111 given branch 'fits' with the specifications passed in here.
112 For example, given branch_specs = ['factory', '>=R18'], we'd set things
113 up so that _FitsSpec() would return True for 'factory', or 'RXX'
114 where XX is a number >= 18.
115
116 Given branch_specs = ['factory', 'firmware'], _FitsSpec()
117 would pass only those two specific strings.
118
119 Example usage:
Chris Masonecc4631d2012-04-20 12:06:39 -0700120 t = Task('Name', 'suite', ['factory', '>=R18'])
Chris Masone96f16632012-04-04 18:36:03 -0700121 t._FitsSpec('factory') # True
122 t._FitsSpec('R19') # True
123 t._FitsSpec('R17') # False
124 t._FitsSpec('firmware') # False
125 t._FitsSpec('goober') # False
126
Chris Masonefad911a2012-03-29 12:30:26 -0700127 @param suite: the name of the suite to run, e.g. 'bvt'
Chris Masone96f16632012-04-04 18:36:03 -0700128 @param branch_specs: a pre-vetted iterable of branch specifiers,
129 e.g. ['>=R18', 'factory']
Chris Masonefad911a2012-03-29 12:30:26 -0700130 @param pool: the pool of machines to use for scheduling purposes.
131 Default: None
132 """
Chris Masonecc4631d2012-04-20 12:06:39 -0700133 self._name = name
Chris Masonefad911a2012-03-29 12:30:26 -0700134 self._suite = suite
Chris Masone96f16632012-04-04 18:36:03 -0700135 self._branch_specs = branch_specs
Chris Masonefad911a2012-03-29 12:30:26 -0700136 self._pool = pool
Chris Masone96f16632012-04-04 18:36:03 -0700137
138 self._bare_branches = []
Chris Masone67f06d62012-04-12 15:16:56 -0700139 if not branch_specs:
140 # Any milestone is OK.
141 self._numeric_constraint = version.LooseVersion('0')
142 else:
143 self._numeric_constraint = None
144 for spec in branch_specs:
145 if spec.startswith('>='):
146 self._numeric_constraint = version.LooseVersion(
147 spec.lstrip('>=R'))
148 else:
149 self._bare_branches.append(spec)
Chris Masonefad911a2012-03-29 12:30:26 -0700150 # Since we expect __hash__() and other comparitor methods to be used
151 # frequently by set operations, and they use str() a lot, pre-compute
152 # the string representation of this object.
Chris Masone3fba86f2012-04-03 10:06:56 -0700153 self._str = '%s: %s on %s with pool %s' % (self.__class__.__name__,
Chris Masone96f16632012-04-04 18:36:03 -0700154 suite, branch_specs, pool)
155
156
157 def _FitsSpec(self, branch):
158 """Checks if a branch is deemed OK by this instance's branch specs.
159
160 When called on a branch name, will return whether that branch
161 'fits' the specifications stored in self._bare_branches and
162 self._numeric_constraint.
163
164 @param branch: the branch to check.
165 @return True if b 'fits' with stored specs, False otherwise.
166 """
Chris Masone657e1552012-05-30 17:06:20 -0700167 if branch in BARE_BRANCHES:
168 return branch in self._bare_branches
169 return (self._numeric_constraint and
170 version.LooseVersion(branch) >= self._numeric_constraint)
Chris Masonefad911a2012-03-29 12:30:26 -0700171
172
173 @property
174 def suite(self):
175 return self._suite
176
177
178 @property
Chris Masone96f16632012-04-04 18:36:03 -0700179 def branch_specs(self):
180 return self._branch_specs
Chris Masonefad911a2012-03-29 12:30:26 -0700181
182
183 @property
Chris Masone3fba86f2012-04-03 10:06:56 -0700184 def pool(self):
185 return self._pool
Chris Masonefad911a2012-03-29 12:30:26 -0700186
187
188 def __str__(self):
189 return self._str
190
191
192 def __lt__(self, other):
193 return str(self) < str(other)
194
195
196 def __le__(self, other):
197 return str(self) <= str(other)
198
199
200 def __eq__(self, other):
201 return str(self) == str(other)
202
203
204 def __ne__(self, other):
205 return str(self) != str(other)
206
207
208 def __gt__(self, other):
209 return str(self) > str(other)
210
211
212 def __ge__(self, other):
213 return str(self) >= str(other)
214
215
216 def __hash__(self):
217 """Allows instances to be correctly deduped when used in a set."""
218 return hash(str(self))
219
220
Alex Miller7ce1b362012-07-10 09:24:10 -0700221 def AvailableHosts(self, scheduler, board):
222 """Query what hosts are able to run a test on a board and pool
223 combination.
Alex Miller511a9e32012-07-03 09:16:47 -0700224
225 @param scheduler: an instance of DedupingScheduler, as defined in
226 deduping_scheduler.py
227 @param board: the board against which one wants to run the test.
Alex Miller7ce1b362012-07-10 09:24:10 -0700228 @return The list of hosts meeting the board and pool requirements,
229 or None if no hosts were found."""
Alex Miller511a9e32012-07-03 09:16:47 -0700230 labels = [Labels.BOARD_PREFIX + board]
231 if self._pool:
Chris Masonecd214e02012-07-10 16:22:10 -0700232 labels.append(Labels.POOL_PREFIX + self._pool)
Alex Miller511a9e32012-07-03 09:16:47 -0700233
Alex Miller7ce1b362012-07-10 09:24:10 -0700234 return scheduler.GetHosts(multiple_labels=labels)
Alex Miller511a9e32012-07-03 09:16:47 -0700235
236
Alex Millerd621cf22012-07-11 13:57:10 -0700237 def ShouldHaveAvailableHosts(self):
238 """As a sanity check, return true if we know for certain that
239 we should be able to schedule this test. If we claim this test
240 should be able to run, and it ends up not being scheduled, then
241 a warning will be reported.
242
243 @return True if this test should be able to run, False otherwise.
244 """
245 return self._pool == 'bvt'
246
247
Chris Masone96f16632012-04-04 18:36:03 -0700248 def Run(self, scheduler, branch_builds, board, force=False):
Chris Masone013859b2012-04-01 13:45:26 -0700249 """Run this task. Returns False if it should be destroyed.
Chris Masonefad911a2012-03-29 12:30:26 -0700250
Chris Masone013859b2012-04-01 13:45:26 -0700251 Execute this task. Attempt to schedule the associated suite.
252 Return True if this task should be kept around, False if it
253 should be destroyed. This allows for one-shot Tasks.
Chris Masonefad911a2012-03-29 12:30:26 -0700254
255 @param scheduler: an instance of DedupingScheduler, as defined in
256 deduping_scheduler.py
Chris Masone05b19442012-04-17 13:37:55 -0700257 @param branch_builds: a dict mapping branch name to the build(s) to
Chris Masone96f16632012-04-04 18:36:03 -0700258 install for that branch, e.g.
Chris Masone05b19442012-04-17 13:37:55 -0700259 {'R18': ['x86-alex-release/R18-1655.0.0'],
260 'R19': ['x86-alex-release/R19-2077.0.0']}
Chris Masone96f16632012-04-04 18:36:03 -0700261 @param board: the board against which to run self._suite.
Chris Masonefad911a2012-03-29 12:30:26 -0700262 @param force: Always schedule the suite.
Chris Masone013859b2012-04-01 13:45:26 -0700263 @return True if the task should be kept, False if not
Chris Masonefad911a2012-03-29 12:30:26 -0700264 """
Chris Masonea3a38172012-05-14 15:19:56 -0700265 logging.info('Running %s on %s', self._name, board)
Chris Masone96f16632012-04-04 18:36:03 -0700266 builds = []
Chris Masonefe5a5092012-04-11 18:29:07 -0700267 for branch, build in branch_builds.iteritems():
Chris Masonea3a38172012-05-14 15:19:56 -0700268 logging.info('Checking if %s fits spec %r',
269 branch, self.branch_specs)
Chris Masone96f16632012-04-04 18:36:03 -0700270 if self._FitsSpec(branch):
Chris Masone05b19442012-04-17 13:37:55 -0700271 builds.extend(build)
Chris Masone96f16632012-04-04 18:36:03 -0700272 for build in builds:
Chris Masone3fba86f2012-04-03 10:06:56 -0700273 try:
Chris Masone96f16632012-04-04 18:36:03 -0700274 if not scheduler.ScheduleSuite(self._suite, board, build,
Chris Masone3fba86f2012-04-03 10:06:56 -0700275 self._pool, force):
Chris Masone96f16632012-04-04 18:36:03 -0700276 logging.info('Skipping scheduling %s on %s for %s',
277 self._suite, build, board)
Chris Masone3fba86f2012-04-03 10:06:56 -0700278 except deduping_scheduler.DedupingSchedulerException as e:
279 logging.error(e)
Chris Masonefad911a2012-03-29 12:30:26 -0700280 return True
281
282
Chris Masone013859b2012-04-01 13:45:26 -0700283class OneShotTask(Task):
284 """A Task that can be run only once. Can schedule itself."""
Chris Masonefad911a2012-03-29 12:30:26 -0700285
286
Chris Masone96f16632012-04-04 18:36:03 -0700287 def Run(self, scheduler, branch_builds, board, force=False):
Chris Masone013859b2012-04-01 13:45:26 -0700288 """Run this task. Returns False, indicating it should be destroyed.
Chris Masonefad911a2012-03-29 12:30:26 -0700289
Chris Masone013859b2012-04-01 13:45:26 -0700290 Run this task. Attempt to schedule the associated suite.
291 Return False, indicating to the caller that it should discard this task.
Chris Masonefad911a2012-03-29 12:30:26 -0700292
293 @param scheduler: an instance of DedupingScheduler, as defined in
294 deduping_scheduler.py
Chris Masone05b19442012-04-17 13:37:55 -0700295 @param branch_builds: a dict mapping branch name to the build(s) to
Chris Masone96f16632012-04-04 18:36:03 -0700296 install for that branch, e.g.
Chris Masone05b19442012-04-17 13:37:55 -0700297 {'R18': ['x86-alex-release/R18-1655.0.0'],
298 'R19': ['x86-alex-release/R19-2077.0.0']}
Chris Masone96f16632012-04-04 18:36:03 -0700299 @param board: the board against which to run self._suite.
Chris Masonefad911a2012-03-29 12:30:26 -0700300 @param force: Always schedule the suite.
301 @return False
302 """
Chris Masone96f16632012-04-04 18:36:03 -0700303 super(OneShotTask, self).Run(scheduler, branch_builds, board, force)
Chris Masonefad911a2012-03-29 12:30:26 -0700304 return False