blob: 7b04c0b6c833894c5241d9c4641019282efbfabd [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 Masone96f16632012-04-04 18:36:03 -07005import deduping_scheduler, forgiving_config_parser
6import logging, re
7
8
9class MalformedConfigEntry(Exception):
10 """Raised to indicate a failure to parse a Task out of a config."""
11 pass
Chris Masonefad911a2012-03-29 12:30:26 -070012
13
Chris Masone013859b2012-04-01 13:45:26 -070014class Task(object):
Chris Masonefad911a2012-03-29 12:30:26 -070015 """Represents an entry from the scheduler config. Can schedule itself.
16
17 Each entry from the scheduler config file maps one-to-one to a
Chris Masone013859b2012-04-01 13:45:26 -070018 Task. Each instance has enough info to schedule itself
Chris Masonefad911a2012-03-29 12:30:26 -070019 on-demand with the AFE.
20
21 This class also overrides __hash__() and all comparitor methods to enable
22 correct use in dicts, sets, etc.
23 """
24
Chris Masone96f16632012-04-04 18:36:03 -070025 _BARE_BRANCHES = ['factory', 'firmware']
Chris Masonefad911a2012-03-29 12:30:26 -070026
Chris Masone96f16632012-04-04 18:36:03 -070027
28 @staticmethod
29 def CreateFromConfigSection(config, section):
30 """Create a Task from a section of a config file.
31
32 The section to parse should look like this:
33 [TaskName]
34 suite: suite_to_run # Required
35 run_on: event_on which to run # Required
36 branch_specs: factory,firmware,>=R12 # Optional
37 pool: pool_of_devices # Optional
38
39 @param config: a ForgivingConfigParser.
40 @param section: the section to parse into a Task.
41 @return keyword, Task object pair. One or both will be None on error.
42 @raise MalformedConfigEntry if there's a problem parsing |section|.
43 """
44 keyword = config.getstring(section, 'run_on')
45 suite = config.getstring(section, 'suite')
46 branches = config.getstring(section, 'branch_specs')
47 pool = config.getstring(section, 'pool')
48 if not keyword:
49 raise MalformedConfigEntry('No event to |run_on|.')
50 if not suite:
51 raise MalformedConfigEntry('No |suite|')
52 specs = []
53 if branches:
54 specs = re.split('\s*,\s*', branches)
55 Task.CheckBranchSpecs(specs)
56 return keyword, Task(suite, specs, pool)
57
58
59 @staticmethod
60 def CheckBranchSpecs(branch_specs):
61 """Make sure entries in the list branch_specs are correctly formed.
62
63 We accept any of Task._BARE_BRANCHES in |branch_specs|, as
64 well as _one_ string of the form '>=RXX', where 'RXX' is a
65 CrOS milestone number.
66
67 @param branch_specs: an iterable of branch specifiers.
68 @raise MalformedConfigEntry if there's a problem parsing |branch_specs|.
69 """
70 have_seen_numeric_constraint = False
71 for branch in branch_specs:
72 if branch in Task._BARE_BRANCHES:
73 continue
74 if branch.startswith('>=R') and not have_seen_numeric_constraint:
75 have_seen_numeric_constraint = True
76 continue
77 raise MalformedConfigEntry('%s is not a valid branch spec.', branch)
78
79
80 def __init__(self, suite, branch_specs, pool=None):
Chris Masonefad911a2012-03-29 12:30:26 -070081 """Constructor
82
Chris Masone96f16632012-04-04 18:36:03 -070083 Given an iterable in |branch_specs|, pre-vetted using CheckBranchSpecs,
84 we'll store them such that _FitsSpec() can be used to check whether a
85 given branch 'fits' with the specifications passed in here.
86 For example, given branch_specs = ['factory', '>=R18'], we'd set things
87 up so that _FitsSpec() would return True for 'factory', or 'RXX'
88 where XX is a number >= 18.
89
90 Given branch_specs = ['factory', 'firmware'], _FitsSpec()
91 would pass only those two specific strings.
92
93 Example usage:
94 t = Task('suite', ['factory', '>=R18'])
95 t._FitsSpec('factory') # True
96 t._FitsSpec('R19') # True
97 t._FitsSpec('R17') # False
98 t._FitsSpec('firmware') # False
99 t._FitsSpec('goober') # False
100
Chris Masonefad911a2012-03-29 12:30:26 -0700101 @param suite: the name of the suite to run, e.g. 'bvt'
Chris Masone96f16632012-04-04 18:36:03 -0700102 @param branch_specs: a pre-vetted iterable of branch specifiers,
103 e.g. ['>=R18', 'factory']
Chris Masonefad911a2012-03-29 12:30:26 -0700104 @param pool: the pool of machines to use for scheduling purposes.
105 Default: None
106 """
107 self._suite = suite
Chris Masone96f16632012-04-04 18:36:03 -0700108 self._branch_specs = branch_specs
Chris Masonefad911a2012-03-29 12:30:26 -0700109 self._pool = pool
Chris Masone96f16632012-04-04 18:36:03 -0700110
111 self._bare_branches = []
112 self._numeric_constraint = ''
113 for spec in branch_specs:
114 if spec.startswith('>='):
115 self._numeric_constraint = spec.lstrip('>=')
116 else:
117 self._bare_branches.append(spec)
118
Chris Masonefad911a2012-03-29 12:30:26 -0700119 # Since we expect __hash__() and other comparitor methods to be used
120 # frequently by set operations, and they use str() a lot, pre-compute
121 # the string representation of this object.
Chris Masone3fba86f2012-04-03 10:06:56 -0700122 self._str = '%s: %s on %s with pool %s' % (self.__class__.__name__,
Chris Masone96f16632012-04-04 18:36:03 -0700123 suite, branch_specs, pool)
124
125
126 def _FitsSpec(self, branch):
127 """Checks if a branch is deemed OK by this instance's branch specs.
128
129 When called on a branch name, will return whether that branch
130 'fits' the specifications stored in self._bare_branches and
131 self._numeric_constraint.
132
133 @param branch: the branch to check.
134 @return True if b 'fits' with stored specs, False otherwise.
135 """
136 return (branch in self._bare_branches or
137 branch >= self._numeric_constraint)
Chris Masonefad911a2012-03-29 12:30:26 -0700138
139
140 @property
141 def suite(self):
142 return self._suite
143
144
145 @property
Chris Masone96f16632012-04-04 18:36:03 -0700146 def branch_specs(self):
147 return self._branch_specs
Chris Masonefad911a2012-03-29 12:30:26 -0700148
149
150 @property
Chris Masone3fba86f2012-04-03 10:06:56 -0700151 def pool(self):
152 return self._pool
Chris Masonefad911a2012-03-29 12:30:26 -0700153
154
155 def __str__(self):
156 return self._str
157
158
159 def __lt__(self, other):
160 return str(self) < str(other)
161
162
163 def __le__(self, other):
164 return str(self) <= str(other)
165
166
167 def __eq__(self, other):
168 return str(self) == str(other)
169
170
171 def __ne__(self, other):
172 return str(self) != str(other)
173
174
175 def __gt__(self, other):
176 return str(self) > str(other)
177
178
179 def __ge__(self, other):
180 return str(self) >= str(other)
181
182
183 def __hash__(self):
184 """Allows instances to be correctly deduped when used in a set."""
185 return hash(str(self))
186
187
Chris Masone96f16632012-04-04 18:36:03 -0700188 def Run(self, scheduler, branch_builds, board, force=False):
Chris Masone013859b2012-04-01 13:45:26 -0700189 """Run this task. Returns False if it should be destroyed.
Chris Masonefad911a2012-03-29 12:30:26 -0700190
Chris Masone013859b2012-04-01 13:45:26 -0700191 Execute this task. Attempt to schedule the associated suite.
192 Return True if this task should be kept around, False if it
193 should be destroyed. This allows for one-shot Tasks.
Chris Masonefad911a2012-03-29 12:30:26 -0700194
195 @param scheduler: an instance of DedupingScheduler, as defined in
196 deduping_scheduler.py
Chris Masone96f16632012-04-04 18:36:03 -0700197 @param branch_builds: a dict mapping branch name to the build to
198 install for that branch, e.g.
199 {'R18': 'x86-alex-release/R18-1655.0.0-a1-b1584',
200 'R19': 'x86-alex-release/R19-2077.0.0-a1-b2056'}
201 @param board: the board against which to run self._suite.
Chris Masonefad911a2012-03-29 12:30:26 -0700202 @param force: Always schedule the suite.
Chris Masone013859b2012-04-01 13:45:26 -0700203 @return True if the task should be kept, False if not
Chris Masonefad911a2012-03-29 12:30:26 -0700204 """
Chris Masone96f16632012-04-04 18:36:03 -0700205 builds = []
206 for branch,build in branch_builds.iteritems():
207 if self._FitsSpec(branch):
208 builds.append(build)
209 for build in builds:
Chris Masone3fba86f2012-04-03 10:06:56 -0700210 try:
Chris Masone96f16632012-04-04 18:36:03 -0700211 if not scheduler.ScheduleSuite(self._suite, board, build,
Chris Masone3fba86f2012-04-03 10:06:56 -0700212 self._pool, force):
Chris Masone96f16632012-04-04 18:36:03 -0700213 logging.info('Skipping scheduling %s on %s for %s',
214 self._suite, build, board)
Chris Masone3fba86f2012-04-03 10:06:56 -0700215 except deduping_scheduler.DedupingSchedulerException as e:
216 logging.error(e)
Chris Masonefad911a2012-03-29 12:30:26 -0700217 return True
218
219
Chris Masone013859b2012-04-01 13:45:26 -0700220class OneShotTask(Task):
221 """A Task that can be run only once. Can schedule itself."""
Chris Masonefad911a2012-03-29 12:30:26 -0700222
223
Chris Masone96f16632012-04-04 18:36:03 -0700224 def Run(self, scheduler, branch_builds, board, force=False):
Chris Masone013859b2012-04-01 13:45:26 -0700225 """Run this task. Returns False, indicating it should be destroyed.
Chris Masonefad911a2012-03-29 12:30:26 -0700226
Chris Masone013859b2012-04-01 13:45:26 -0700227 Run this task. Attempt to schedule the associated suite.
228 Return False, indicating to the caller that it should discard this task.
Chris Masonefad911a2012-03-29 12:30:26 -0700229
230 @param scheduler: an instance of DedupingScheduler, as defined in
231 deduping_scheduler.py
Chris Masone96f16632012-04-04 18:36:03 -0700232 @param branch_builds: a dict mapping branch name to the build to
233 install for that branch, e.g.
234 {'beta': 'x86-alex-release/R18-1655.0.0-a1-b1584',
235 'dev': 'x86-alex-release/R19-2077.0.0-a1-b2056'}
236 @param board: the board against which to run self._suite.
Chris Masonefad911a2012-03-29 12:30:26 -0700237 @param force: Always schedule the suite.
238 @return False
239 """
Chris Masone96f16632012-04-04 18:36:03 -0700240 super(OneShotTask, self).Run(scheduler, branch_builds, board, force)
Chris Masonefad911a2012-03-29 12:30:26 -0700241 return False