blob: 346989ef308fa311a07a168d4cc3e23c0ccda37d [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 """
56 keyword = config.getstring(section, 'run_on')
57 suite = config.getstring(section, 'suite')
58 branches = config.getstring(section, 'branch_specs')
59 pool = config.getstring(section, 'pool')
60 if not keyword:
61 raise MalformedConfigEntry('No event to |run_on|.')
62 if not suite:
63 raise MalformedConfigEntry('No |suite|')
64 specs = []
65 if branches:
66 specs = re.split('\s*,\s*', branches)
67 Task.CheckBranchSpecs(specs)
Chris Masonecc4631d2012-04-20 12:06:39 -070068 return keyword, Task(section, suite, specs, pool)
Chris Masone96f16632012-04-04 18:36:03 -070069
70
71 @staticmethod
72 def CheckBranchSpecs(branch_specs):
73 """Make sure entries in the list branch_specs are correctly formed.
74
Chris Masone67f06d62012-04-12 15:16:56 -070075 We accept any of BARE_BRANCHES in |branch_specs|, as
Chris Masone96f16632012-04-04 18:36:03 -070076 well as _one_ string of the form '>=RXX', where 'RXX' is a
77 CrOS milestone number.
78
79 @param branch_specs: an iterable of branch specifiers.
80 @raise MalformedConfigEntry if there's a problem parsing |branch_specs|.
81 """
82 have_seen_numeric_constraint = False
83 for branch in branch_specs:
Chris Masone67f06d62012-04-12 15:16:56 -070084 if branch in BARE_BRANCHES:
Chris Masone96f16632012-04-04 18:36:03 -070085 continue
86 if branch.startswith('>=R') and not have_seen_numeric_constraint:
87 have_seen_numeric_constraint = True
88 continue
Chris Masone97cf0a72012-05-16 09:55:52 -070089 raise MalformedConfigEntry("%s isn't a valid branch spec." % branch)
Chris Masone96f16632012-04-04 18:36:03 -070090
91
Chris Masonecc4631d2012-04-20 12:06:39 -070092 def __init__(self, name, suite, branch_specs, pool=None):
Chris Masonefad911a2012-03-29 12:30:26 -070093 """Constructor
94
Chris Masone96f16632012-04-04 18:36:03 -070095 Given an iterable in |branch_specs|, pre-vetted using CheckBranchSpecs,
96 we'll store them such that _FitsSpec() can be used to check whether a
97 given branch 'fits' with the specifications passed in here.
98 For example, given branch_specs = ['factory', '>=R18'], we'd set things
99 up so that _FitsSpec() would return True for 'factory', or 'RXX'
100 where XX is a number >= 18.
101
102 Given branch_specs = ['factory', 'firmware'], _FitsSpec()
103 would pass only those two specific strings.
104
105 Example usage:
Chris Masonecc4631d2012-04-20 12:06:39 -0700106 t = Task('Name', 'suite', ['factory', '>=R18'])
Chris Masone96f16632012-04-04 18:36:03 -0700107 t._FitsSpec('factory') # True
108 t._FitsSpec('R19') # True
109 t._FitsSpec('R17') # False
110 t._FitsSpec('firmware') # False
111 t._FitsSpec('goober') # False
112
Chris Masonefad911a2012-03-29 12:30:26 -0700113 @param suite: the name of the suite to run, e.g. 'bvt'
Chris Masone96f16632012-04-04 18:36:03 -0700114 @param branch_specs: a pre-vetted iterable of branch specifiers,
115 e.g. ['>=R18', 'factory']
Chris Masonefad911a2012-03-29 12:30:26 -0700116 @param pool: the pool of machines to use for scheduling purposes.
117 Default: None
118 """
Chris Masonecc4631d2012-04-20 12:06:39 -0700119 self._name = name
Chris Masonefad911a2012-03-29 12:30:26 -0700120 self._suite = suite
Chris Masone96f16632012-04-04 18:36:03 -0700121 self._branch_specs = branch_specs
Chris Masonefad911a2012-03-29 12:30:26 -0700122 self._pool = pool
Chris Masone96f16632012-04-04 18:36:03 -0700123
124 self._bare_branches = []
Chris Masone67f06d62012-04-12 15:16:56 -0700125 if not branch_specs:
126 # Any milestone is OK.
127 self._numeric_constraint = version.LooseVersion('0')
128 else:
129 self._numeric_constraint = None
130 for spec in branch_specs:
131 if spec.startswith('>='):
132 self._numeric_constraint = version.LooseVersion(
133 spec.lstrip('>=R'))
134 else:
135 self._bare_branches.append(spec)
Chris Masonefad911a2012-03-29 12:30:26 -0700136 # Since we expect __hash__() and other comparitor methods to be used
137 # frequently by set operations, and they use str() a lot, pre-compute
138 # the string representation of this object.
Chris Masone3fba86f2012-04-03 10:06:56 -0700139 self._str = '%s: %s on %s with pool %s' % (self.__class__.__name__,
Chris Masone96f16632012-04-04 18:36:03 -0700140 suite, branch_specs, pool)
141
142
143 def _FitsSpec(self, branch):
144 """Checks if a branch is deemed OK by this instance's branch specs.
145
146 When called on a branch name, will return whether that branch
147 'fits' the specifications stored in self._bare_branches and
148 self._numeric_constraint.
149
150 @param branch: the branch to check.
151 @return True if b 'fits' with stored specs, False otherwise.
152 """
Chris Masone657e1552012-05-30 17:06:20 -0700153 if branch in BARE_BRANCHES:
154 return branch in self._bare_branches
155 return (self._numeric_constraint and
156 version.LooseVersion(branch) >= self._numeric_constraint)
Chris Masonefad911a2012-03-29 12:30:26 -0700157
158
159 @property
160 def suite(self):
161 return self._suite
162
163
164 @property
Chris Masone96f16632012-04-04 18:36:03 -0700165 def branch_specs(self):
166 return self._branch_specs
Chris Masonefad911a2012-03-29 12:30:26 -0700167
168
169 @property
Chris Masone3fba86f2012-04-03 10:06:56 -0700170 def pool(self):
171 return self._pool
Chris Masonefad911a2012-03-29 12:30:26 -0700172
173
174 def __str__(self):
175 return self._str
176
177
178 def __lt__(self, other):
179 return str(self) < str(other)
180
181
182 def __le__(self, other):
183 return str(self) <= str(other)
184
185
186 def __eq__(self, other):
187 return str(self) == str(other)
188
189
190 def __ne__(self, other):
191 return str(self) != str(other)
192
193
194 def __gt__(self, other):
195 return str(self) > str(other)
196
197
198 def __ge__(self, other):
199 return str(self) >= str(other)
200
201
202 def __hash__(self):
203 """Allows instances to be correctly deduped when used in a set."""
204 return hash(str(self))
205
206
Alex Miller511a9e32012-07-03 09:16:47 -0700207 def CanRun(self, scheduler, board):
208 """Check and see if this test is able to be scheduled on this board.
209
210 @param scheduler: an instance of DedupingScheduler, as defined in
211 deduping_scheduler.py
212 @param board: the board against which one wants to run the test.
213 @return True if the test can be successfully scheduled, false if
214 scheduling the test will result in the test never being run."""
215 labels = [Labels.BOARD_PREFIX + board]
216 if self._pool:
217 labels.append(Labels.POOL_PREFIX + self._pool)
218
219 hosts = scheduler.GetHosts(multiple_labels=labels)
220 return len(hosts) != 0
221
222
Chris Masone96f16632012-04-04 18:36:03 -0700223 def Run(self, scheduler, branch_builds, board, force=False):
Chris Masone013859b2012-04-01 13:45:26 -0700224 """Run this task. Returns False if it should be destroyed.
Chris Masonefad911a2012-03-29 12:30:26 -0700225
Chris Masone013859b2012-04-01 13:45:26 -0700226 Execute this task. Attempt to schedule the associated suite.
227 Return True if this task should be kept around, False if it
228 should be destroyed. This allows for one-shot Tasks.
Chris Masonefad911a2012-03-29 12:30:26 -0700229
230 @param scheduler: an instance of DedupingScheduler, as defined in
231 deduping_scheduler.py
Chris Masone05b19442012-04-17 13:37:55 -0700232 @param branch_builds: a dict mapping branch name to the build(s) to
Chris Masone96f16632012-04-04 18:36:03 -0700233 install for that branch, e.g.
Chris Masone05b19442012-04-17 13:37:55 -0700234 {'R18': ['x86-alex-release/R18-1655.0.0'],
235 'R19': ['x86-alex-release/R19-2077.0.0']}
Chris Masone96f16632012-04-04 18:36:03 -0700236 @param board: the board against which to run self._suite.
Chris Masonefad911a2012-03-29 12:30:26 -0700237 @param force: Always schedule the suite.
Chris Masone013859b2012-04-01 13:45:26 -0700238 @return True if the task should be kept, False if not
Chris Masonefad911a2012-03-29 12:30:26 -0700239 """
Chris Masonea3a38172012-05-14 15:19:56 -0700240 logging.info('Running %s on %s', self._name, board)
Chris Masone96f16632012-04-04 18:36:03 -0700241 builds = []
Chris Masonefe5a5092012-04-11 18:29:07 -0700242 for branch, build in branch_builds.iteritems():
Chris Masonea3a38172012-05-14 15:19:56 -0700243 logging.info('Checking if %s fits spec %r',
244 branch, self.branch_specs)
Chris Masone96f16632012-04-04 18:36:03 -0700245 if self._FitsSpec(branch):
Chris Masone05b19442012-04-17 13:37:55 -0700246 builds.extend(build)
Chris Masone96f16632012-04-04 18:36:03 -0700247 for build in builds:
Chris Masone3fba86f2012-04-03 10:06:56 -0700248 try:
Chris Masone96f16632012-04-04 18:36:03 -0700249 if not scheduler.ScheduleSuite(self._suite, board, build,
Chris Masone3fba86f2012-04-03 10:06:56 -0700250 self._pool, force):
Chris Masone96f16632012-04-04 18:36:03 -0700251 logging.info('Skipping scheduling %s on %s for %s',
252 self._suite, build, board)
Chris Masone3fba86f2012-04-03 10:06:56 -0700253 except deduping_scheduler.DedupingSchedulerException as e:
254 logging.error(e)
Chris Masonefad911a2012-03-29 12:30:26 -0700255 return True
256
257
Chris Masone013859b2012-04-01 13:45:26 -0700258class OneShotTask(Task):
259 """A Task that can be run only once. Can schedule itself."""
Chris Masonefad911a2012-03-29 12:30:26 -0700260
261
Chris Masone96f16632012-04-04 18:36:03 -0700262 def Run(self, scheduler, branch_builds, board, force=False):
Chris Masone013859b2012-04-01 13:45:26 -0700263 """Run this task. Returns False, indicating it should be destroyed.
Chris Masonefad911a2012-03-29 12:30:26 -0700264
Chris Masone013859b2012-04-01 13:45:26 -0700265 Run this task. Attempt to schedule the associated suite.
266 Return False, indicating to the caller that it should discard this task.
Chris Masonefad911a2012-03-29 12:30:26 -0700267
268 @param scheduler: an instance of DedupingScheduler, as defined in
269 deduping_scheduler.py
Chris Masone05b19442012-04-17 13:37:55 -0700270 @param branch_builds: a dict mapping branch name to the build(s) to
Chris Masone96f16632012-04-04 18:36:03 -0700271 install for that branch, e.g.
Chris Masone05b19442012-04-17 13:37:55 -0700272 {'R18': ['x86-alex-release/R18-1655.0.0'],
273 'R19': ['x86-alex-release/R19-2077.0.0']}
Chris Masone96f16632012-04-04 18:36:03 -0700274 @param board: the board against which to run self._suite.
Chris Masonefad911a2012-03-29 12:30:26 -0700275 @param force: Always schedule the suite.
276 @return False
277 """
Chris Masone96f16632012-04-04 18:36:03 -0700278 super(OneShotTask, self).Run(scheduler, branch_builds, board, force)
Chris Masonefad911a2012-03-29 12:30:26 -0700279 return False