blob: 4dbc3833a5a182370b8eb7f9e884381772ce5d5b [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
Chris Masone96f16632012-04-04 18:36:03 -07009
10
11class MalformedConfigEntry(Exception):
12 """Raised to indicate a failure to parse a Task out of a config."""
13 pass
Chris Masonefad911a2012-03-29 12:30:26 -070014
15
Chris Masone67f06d62012-04-12 15:16:56 -070016BARE_BRANCHES = ['factory', 'firmware']
17
18
19def PickBranchName(type, milestone):
20 if type in BARE_BRANCHES:
21 return type
22 return milestone
23
24
Chris Masone013859b2012-04-01 13:45:26 -070025class Task(object):
Chris Masonefad911a2012-03-29 12:30:26 -070026 """Represents an entry from the scheduler config. Can schedule itself.
27
28 Each entry from the scheduler config file maps one-to-one to a
Chris Masone013859b2012-04-01 13:45:26 -070029 Task. Each instance has enough info to schedule itself
Chris Masonefad911a2012-03-29 12:30:26 -070030 on-demand with the AFE.
31
32 This class also overrides __hash__() and all comparitor methods to enable
33 correct use in dicts, sets, etc.
34 """
35
Chris Masone96f16632012-04-04 18:36:03 -070036
37 @staticmethod
38 def CreateFromConfigSection(config, section):
39 """Create a Task from a section of a config file.
40
41 The section to parse should look like this:
42 [TaskName]
43 suite: suite_to_run # Required
44 run_on: event_on which to run # Required
45 branch_specs: factory,firmware,>=R12 # Optional
46 pool: pool_of_devices # Optional
47
Chris Masone67f06d62012-04-12 15:16:56 -070048 By default, Tasks run on all release branches, not factory or firmware.
49
Chris Masone96f16632012-04-04 18:36:03 -070050 @param config: a ForgivingConfigParser.
51 @param section: the section to parse into a Task.
52 @return keyword, Task object pair. One or both will be None on error.
53 @raise MalformedConfigEntry if there's a problem parsing |section|.
54 """
55 keyword = config.getstring(section, 'run_on')
56 suite = config.getstring(section, 'suite')
57 branches = config.getstring(section, 'branch_specs')
58 pool = config.getstring(section, 'pool')
59 if not keyword:
60 raise MalformedConfigEntry('No event to |run_on|.')
61 if not suite:
62 raise MalformedConfigEntry('No |suite|')
63 specs = []
64 if branches:
65 specs = re.split('\s*,\s*', branches)
66 Task.CheckBranchSpecs(specs)
Chris Masonecc4631d2012-04-20 12:06:39 -070067 return keyword, Task(section, suite, specs, pool)
Chris Masone96f16632012-04-04 18:36:03 -070068
69
70 @staticmethod
71 def CheckBranchSpecs(branch_specs):
72 """Make sure entries in the list branch_specs are correctly formed.
73
Chris Masone67f06d62012-04-12 15:16:56 -070074 We accept any of BARE_BRANCHES in |branch_specs|, as
Chris Masone96f16632012-04-04 18:36:03 -070075 well as _one_ string of the form '>=RXX', where 'RXX' is a
76 CrOS milestone number.
77
78 @param branch_specs: an iterable of branch specifiers.
79 @raise MalformedConfigEntry if there's a problem parsing |branch_specs|.
80 """
81 have_seen_numeric_constraint = False
82 for branch in branch_specs:
Chris Masone67f06d62012-04-12 15:16:56 -070083 if branch in BARE_BRANCHES:
Chris Masone96f16632012-04-04 18:36:03 -070084 continue
85 if branch.startswith('>=R') and not have_seen_numeric_constraint:
86 have_seen_numeric_constraint = True
87 continue
Chris Masone97cf0a72012-05-16 09:55:52 -070088 raise MalformedConfigEntry("%s isn't a valid branch spec." % branch)
Chris Masone96f16632012-04-04 18:36:03 -070089
90
Chris Masonecc4631d2012-04-20 12:06:39 -070091 def __init__(self, name, suite, branch_specs, pool=None):
Chris Masonefad911a2012-03-29 12:30:26 -070092 """Constructor
93
Chris Masone96f16632012-04-04 18:36:03 -070094 Given an iterable in |branch_specs|, pre-vetted using CheckBranchSpecs,
95 we'll store them such that _FitsSpec() can be used to check whether a
96 given branch 'fits' with the specifications passed in here.
97 For example, given branch_specs = ['factory', '>=R18'], we'd set things
98 up so that _FitsSpec() would return True for 'factory', or 'RXX'
99 where XX is a number >= 18.
100
101 Given branch_specs = ['factory', 'firmware'], _FitsSpec()
102 would pass only those two specific strings.
103
104 Example usage:
Chris Masonecc4631d2012-04-20 12:06:39 -0700105 t = Task('Name', 'suite', ['factory', '>=R18'])
Chris Masone96f16632012-04-04 18:36:03 -0700106 t._FitsSpec('factory') # True
107 t._FitsSpec('R19') # True
108 t._FitsSpec('R17') # False
109 t._FitsSpec('firmware') # False
110 t._FitsSpec('goober') # False
111
Chris Masonefad911a2012-03-29 12:30:26 -0700112 @param suite: the name of the suite to run, e.g. 'bvt'
Chris Masone96f16632012-04-04 18:36:03 -0700113 @param branch_specs: a pre-vetted iterable of branch specifiers,
114 e.g. ['>=R18', 'factory']
Chris Masonefad911a2012-03-29 12:30:26 -0700115 @param pool: the pool of machines to use for scheduling purposes.
116 Default: None
117 """
Chris Masonecc4631d2012-04-20 12:06:39 -0700118 self._name = name
Chris Masonefad911a2012-03-29 12:30:26 -0700119 self._suite = suite
Chris Masone96f16632012-04-04 18:36:03 -0700120 self._branch_specs = branch_specs
Chris Masonefad911a2012-03-29 12:30:26 -0700121 self._pool = pool
Chris Masone96f16632012-04-04 18:36:03 -0700122
123 self._bare_branches = []
Chris Masone67f06d62012-04-12 15:16:56 -0700124 if not branch_specs:
125 # Any milestone is OK.
126 self._numeric_constraint = version.LooseVersion('0')
127 else:
128 self._numeric_constraint = None
129 for spec in branch_specs:
130 if spec.startswith('>='):
131 self._numeric_constraint = version.LooseVersion(
132 spec.lstrip('>=R'))
133 else:
134 self._bare_branches.append(spec)
Chris Masone96f16632012-04-04 18:36:03 -0700135
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 """
153 return (branch in self._bare_branches or
Chris Masone67f06d62012-04-12 15:16:56 -0700154 (self._numeric_constraint and
155 version.LooseVersion(branch) >= self._numeric_constraint))
Chris Masonefad911a2012-03-29 12:30:26 -0700156
157
158 @property
159 def suite(self):
160 return self._suite
161
162
163 @property
Chris Masone96f16632012-04-04 18:36:03 -0700164 def branch_specs(self):
165 return self._branch_specs
Chris Masonefad911a2012-03-29 12:30:26 -0700166
167
168 @property
Chris Masone3fba86f2012-04-03 10:06:56 -0700169 def pool(self):
170 return self._pool
Chris Masonefad911a2012-03-29 12:30:26 -0700171
172
173 def __str__(self):
174 return self._str
175
176
177 def __lt__(self, other):
178 return str(self) < str(other)
179
180
181 def __le__(self, other):
182 return str(self) <= str(other)
183
184
185 def __eq__(self, other):
186 return str(self) == str(other)
187
188
189 def __ne__(self, other):
190 return str(self) != str(other)
191
192
193 def __gt__(self, other):
194 return str(self) > str(other)
195
196
197 def __ge__(self, other):
198 return str(self) >= str(other)
199
200
201 def __hash__(self):
202 """Allows instances to be correctly deduped when used in a set."""
203 return hash(str(self))
204
205
Chris Masone96f16632012-04-04 18:36:03 -0700206 def Run(self, scheduler, branch_builds, board, force=False):
Chris Masone013859b2012-04-01 13:45:26 -0700207 """Run this task. Returns False if it should be destroyed.
Chris Masonefad911a2012-03-29 12:30:26 -0700208
Chris Masone013859b2012-04-01 13:45:26 -0700209 Execute this task. Attempt to schedule the associated suite.
210 Return True if this task should be kept around, False if it
211 should be destroyed. This allows for one-shot Tasks.
Chris Masonefad911a2012-03-29 12:30:26 -0700212
213 @param scheduler: an instance of DedupingScheduler, as defined in
214 deduping_scheduler.py
Chris Masone05b19442012-04-17 13:37:55 -0700215 @param branch_builds: a dict mapping branch name to the build(s) to
Chris Masone96f16632012-04-04 18:36:03 -0700216 install for that branch, e.g.
Chris Masone05b19442012-04-17 13:37:55 -0700217 {'R18': ['x86-alex-release/R18-1655.0.0'],
218 'R19': ['x86-alex-release/R19-2077.0.0']}
Chris Masone96f16632012-04-04 18:36:03 -0700219 @param board: the board against which to run self._suite.
Chris Masonefad911a2012-03-29 12:30:26 -0700220 @param force: Always schedule the suite.
Chris Masone013859b2012-04-01 13:45:26 -0700221 @return True if the task should be kept, False if not
Chris Masonefad911a2012-03-29 12:30:26 -0700222 """
Chris Masonea3a38172012-05-14 15:19:56 -0700223 logging.info('Running %s on %s', self._name, board)
Chris Masone96f16632012-04-04 18:36:03 -0700224 builds = []
Chris Masonefe5a5092012-04-11 18:29:07 -0700225 for branch, build in branch_builds.iteritems():
Chris Masonea3a38172012-05-14 15:19:56 -0700226 logging.info('Checking if %s fits spec %r',
227 branch, self.branch_specs)
Chris Masone96f16632012-04-04 18:36:03 -0700228 if self._FitsSpec(branch):
Chris Masone05b19442012-04-17 13:37:55 -0700229 builds.extend(build)
Chris Masone96f16632012-04-04 18:36:03 -0700230 for build in builds:
Chris Masone3fba86f2012-04-03 10:06:56 -0700231 try:
Chris Masone96f16632012-04-04 18:36:03 -0700232 if not scheduler.ScheduleSuite(self._suite, board, build,
Chris Masone3fba86f2012-04-03 10:06:56 -0700233 self._pool, force):
Chris Masone96f16632012-04-04 18:36:03 -0700234 logging.info('Skipping scheduling %s on %s for %s',
235 self._suite, build, board)
Chris Masone3fba86f2012-04-03 10:06:56 -0700236 except deduping_scheduler.DedupingSchedulerException as e:
237 logging.error(e)
Chris Masonefad911a2012-03-29 12:30:26 -0700238 return True
239
240
Chris Masone013859b2012-04-01 13:45:26 -0700241class OneShotTask(Task):
242 """A Task that can be run only once. Can schedule itself."""
Chris Masonefad911a2012-03-29 12:30:26 -0700243
244
Chris Masone96f16632012-04-04 18:36:03 -0700245 def Run(self, scheduler, branch_builds, board, force=False):
Chris Masone013859b2012-04-01 13:45:26 -0700246 """Run this task. Returns False, indicating it should be destroyed.
Chris Masonefad911a2012-03-29 12:30:26 -0700247
Chris Masone013859b2012-04-01 13:45:26 -0700248 Run this task. Attempt to schedule the associated suite.
249 Return False, indicating to the caller that it should discard this task.
Chris Masonefad911a2012-03-29 12:30:26 -0700250
251 @param scheduler: an instance of DedupingScheduler, as defined in
252 deduping_scheduler.py
Chris Masone05b19442012-04-17 13:37:55 -0700253 @param branch_builds: a dict mapping branch name to the build(s) to
Chris Masone96f16632012-04-04 18:36:03 -0700254 install for that branch, e.g.
Chris Masone05b19442012-04-17 13:37:55 -0700255 {'R18': ['x86-alex-release/R18-1655.0.0'],
256 'R19': ['x86-alex-release/R19-2077.0.0']}
Chris Masone96f16632012-04-04 18:36:03 -0700257 @param board: the board against which to run self._suite.
Chris Masonefad911a2012-03-29 12:30:26 -0700258 @param force: Always schedule the suite.
259 @return False
260 """
Chris Masone96f16632012-04-04 18:36:03 -0700261 super(OneShotTask, self).Run(scheduler, branch_builds, board, force)
Chris Masonefad911a2012-03-29 12:30:26 -0700262 return False