| 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 |