OLD | NEW |
| (Empty) |
1 # coding=utf8 | |
2 # Copyright (c) 2013 The Chromium Authors. All rights reserved. | |
3 # Use of this source code is governed by a BSD-style license that can be | |
4 # found in the LICENSE file. | |
5 | |
6 import model | |
7 | |
8 | |
9 # CQ uses this delay to decide if a triggered job hasn't show up from | |
10 # rietveld processing time or if it has gone missing | |
11 PROPOGATION_DELAY_S = 3 * 60 * 60 | |
12 | |
13 | |
14 def need_to_trigger(builder_name, need_to_run, try_jobs): | |
15 """Returns which tests need to be triggered. | |
16 | |
17 These are the tests that are not pending on any try job, either running or | |
18 in the pending list. | |
19 """ | |
20 need_to_run = set(need_to_run) | |
21 for try_job in try_jobs: | |
22 if try_job.builder == builder_name: | |
23 need_to_run -= set(try_job.steps_passed) | |
24 if not try_job.completed: | |
25 if try_job.requested_steps == []: | |
26 # Special case jobs discovered by CQ that it did not send. Wait for | |
27 # these jobs to complete rather than trying to interpret the filter | |
28 assert try_job.started | |
29 need_to_run.clear() | |
30 else: | |
31 need_to_run -= ( | |
32 set(try_job.requested_steps) - set(try_job.steps_failed)) | |
33 | |
34 return need_to_run | |
35 | |
36 | |
37 def waiting_for(builder_name, tests, try_jobs): | |
38 """Returns the tests that we are waiting for results on pending or running | |
39 builds. | |
40 """ | |
41 tests = set(tests) | |
42 for try_job in try_jobs: | |
43 if try_job.builder == builder_name: | |
44 tests -= set(try_job.steps_passed) | |
45 return tests | |
46 | |
47 | |
48 class TryJobStepsBase(model.PersistentMixIn): | |
49 builder_name = unicode | |
50 | |
51 # Name of the prerequisite builder. | |
52 prereq_builder = unicode | |
53 # List of prerequisite tests to look for. | |
54 prereq_tests = list | |
55 | |
56 def __init__(self, **kwargs): | |
57 kwargs.setdefault('prereq_builder', u'') | |
58 kwargs.setdefault('prereq_tests', []) | |
59 required = set(self._persistent_members()) | |
60 actual = set(kwargs) | |
61 assert required == actual, (required - actual, required, actual) | |
62 super(TryJobStepsBase, self).__init__(**kwargs) | |
63 # Then mark it read-only. | |
64 self._read_only = True | |
65 | |
66 @model.immutable | |
67 def unmet_prereqs(self, try_jobs): | |
68 """ | |
69 Determine if this TryJobSteps has unmet prerequisites. | |
70 | |
71 Returns True iff prereq is unmet. | |
72 """ | |
73 if not self.prereq_builder or not self.prereq_tests: | |
74 return None | |
75 unmet_steps = set(self.prereq_tests) | |
76 for try_job in try_jobs.itervalues(): | |
77 if try_job.builder == self.prereq_builder: | |
78 unmet_steps -= set(try_job.steps_passed) | |
79 return bool(unmet_steps) | |
80 | |
81 @model.immutable | |
82 def get_triggered_steps(self, _builder, _steps): | |
83 """Returns the steps on this builder that will get triggered by the given | |
84 builder and its steps, which is always None since this isn't a triggered | |
85 bot.""" | |
86 return (self.builder_name, []) | |
87 | |
88 | |
89 class TryJobSteps(TryJobStepsBase): | |
90 steps = list | |
91 | |
92 @model.immutable | |
93 def waiting_for(self, try_jobs): | |
94 """Returns the tests that we are waiting for results on pending or running | |
95 builds. | |
96 """ | |
97 return (self.builder_name, | |
98 waiting_for(self.builder_name, self.steps, try_jobs.itervalues())) | |
99 | |
100 @model.immutable | |
101 def need_to_trigger(self, try_jobs, _now): | |
102 """Returns which tests need to be triggered. | |
103 | |
104 These are the tests that are not pending on any try job, either running or | |
105 in the pending list. | |
106 """ | |
107 if self.unmet_prereqs(try_jobs): | |
108 return (self.builder_name, []) | |
109 return (self.builder_name, | |
110 need_to_trigger(self.builder_name, self.steps, | |
111 try_jobs.itervalues())) | |
112 | |
113 | |
114 class TryJobTriggeredSteps(TryJobStepsBase): | |
115 # The name of the bot that triggers this bot. | |
116 trigger_name = unicode | |
117 # Maps the triggered_bot_step -> trigger_bot_step required to trigger it. | |
118 steps = dict | |
119 | |
120 @model.immutable | |
121 def _triggered_try_jobs(self, try_jobs): | |
122 """Return all the try jobs that were on this builder and had been trigger | |
123 by the trigger_name bot.""" | |
124 triggered_try_jobs = [] | |
125 for try_job in try_jobs.itervalues(): | |
126 if (try_job.builder == self.builder_name and | |
127 try_job.parent_key and try_job.parent_key in try_jobs and | |
128 try_jobs[try_job.parent_key].builder == self.trigger_name): | |
129 triggered_try_jobs.append(try_job) | |
130 | |
131 return triggered_try_jobs | |
132 | |
133 @model.immutable | |
134 def waiting_for(self, try_jobs): | |
135 """Returns the tests that we are waiting for results on pending or running | |
136 builds. | |
137 """ | |
138 return (self.builder_name, | |
139 waiting_for(self.builder_name, self.steps, | |
140 self._triggered_try_jobs(try_jobs))) | |
141 | |
142 @model.immutable | |
143 def need_to_trigger(self, try_jobs, now): | |
144 """Returns which tests need to be triggered. | |
145 | |
146 These are the tests that are not pending on any try job, either running or | |
147 in the pending list. | |
148 """ | |
149 if self.unmet_prereqs(try_jobs): | |
150 return (self.builder_name, []) | |
151 need_to_run = set(self.steps) | |
152 steps_to_trigger = need_to_trigger(self.builder_name, need_to_run, | |
153 self._triggered_try_jobs(try_jobs)) | |
154 | |
155 # Convert the steps to trigger to their trigger options. | |
156 trigger_need_to_run = set(self.steps[step] for step in steps_to_trigger) | |
157 | |
158 # See which triggered builds have already started, so we can then ignore | |
159 # their parents when seeing if more triggered build are on the way. | |
160 detected_triggered_keys = set( | |
161 job.parent_key for key, job in try_jobs.iteritems() | |
162 if job.builder == self.builder_name) | |
163 | |
164 # Remove any trigger options that are waiting to run from the set to | |
165 # trigger. | |
166 for key, try_job in try_jobs.iteritems(): | |
167 if try_job.builder == self.trigger_name: | |
168 if (try_job.completed and | |
169 not (now - try_job.init_time > PROPOGATION_DELAY_S) and | |
170 key not in detected_triggered_keys): | |
171 # If we get here a triggered build hasn't started yet, so wait for | |
172 # any steps that should run on the triggered build. | |
173 trigger_need_to_run -= ( | |
174 set(step for step in self.steps.itervalues() | |
175 if step in try_job.requested_steps)) | |
176 if not try_job.completed: | |
177 trigger_need_to_run -= ( | |
178 set(try_job.requested_steps) - set(try_job.steps_failed)) | |
179 | |
180 return (self.trigger_name, trigger_need_to_run) | |
181 | |
182 @model.immutable | |
183 def get_triggered_steps(self, builder, steps): | |
184 """Returns the steps on this builder that will get triggered by the given | |
185 builder and its steps.""" | |
186 trigger_steps = [] | |
187 if builder == self.trigger_name: | |
188 trigger_steps = [key for key, value in self.steps.iteritems() | |
189 if value in steps] | |
190 | |
191 return (self.builder_name, sorted(trigger_steps)) | |
192 | |
193 | |
194 class TryJobTriggeredOrNormalSteps(TryJobTriggeredSteps): | |
195 """This class assumes that the triggered names can be run | |
196 on the trigger bot with the same name that they appear with on | |
197 the triggered bot.""" | |
198 | |
199 # The list of steps that have to be run on the trigger bot. | |
200 trigger_bot_steps = list | |
201 | |
202 # True if the triggered bot should try and run the missing tests. | |
203 use_triggered_bot = bool | |
204 | |
205 @model.immutable | |
206 def need_to_trigger(self, try_jobs, now): | |
207 if self.unmet_prereqs(try_jobs): | |
208 return (self.builder_name, []) | |
209 name, steps = super(TryJobTriggeredOrNormalSteps, | |
210 self).need_to_trigger(try_jobs, now) | |
211 | |
212 # Remove any tests where that could still be triggered by the trigger bot. | |
213 steps = need_to_trigger(self.trigger_name, steps, try_jobs.itervalues()) | |
214 | |
215 # Convert the trigger names to the triggered names. | |
216 steps = set(step for step, trigger | |
217 in self.steps.iteritems() if trigger in steps) | |
218 | |
219 # Add the trigger bot only steps and remove ones that have passed. | |
220 steps = steps.union(self.trigger_bot_steps) | |
221 steps = need_to_trigger(self.trigger_name, steps, try_jobs.itervalues()) | |
222 | |
223 # If we want to use the triggered bot, convert the steps backs to their | |
224 # trigger name (where possible). | |
225 if self.use_triggered_bot: | |
226 # TODO(csharp): Remove this once the triggered bots should always handle | |
227 # retry (limit it to one attempt each to prevent too much breakage). | |
228 if any(try_job.builder == self.builder_name | |
229 for try_job in try_jobs.itervalues()): | |
230 return name, steps | |
231 | |
232 steps = set(self.steps.get(step, step) for step in steps) | |
233 | |
234 return name, steps | |
235 | |
236 @model.immutable | |
237 def waiting_for(self, try_jobs): | |
238 steps = waiting_for(self.builder_name, self.steps, | |
239 self._triggered_try_jobs(try_jobs)) | |
240 | |
241 # Add the steps that can only run on the trigger bot and see what hasn't | |
242 # passed on the trigger bot yet. | |
243 steps = steps.union(self.trigger_bot_steps) | |
244 | |
245 steps = waiting_for(self.trigger_name, steps, try_jobs.itervalues()) | |
246 | |
247 return self.trigger_name, steps | |
OLD | NEW |