blob: 1a7c421c2056cc27750b10070b367162ff0b28ad [file] [log] [blame]
James E. Blairee743612012-05-29 14:49:32 -07001# Copyright 2012 Hewlett-Packard Development Company, L.P.
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may
4# not use this file except in compliance with the License. You may obtain
5# a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations
13# under the License.
14
15import re
James E. Blairff986a12012-05-30 14:56:51 -070016import time
James E. Blair4886cc12012-07-18 15:39:41 -070017from uuid import uuid4
James E. Blair5a9918a2013-08-27 10:06:27 -070018import extras
19
20OrderedDict = extras.try_imports(['collections.OrderedDict',
21 'ordereddict.OrderedDict'])
James E. Blair4886cc12012-07-18 15:39:41 -070022
23
James E. Blair19deff22013-08-25 13:17:35 -070024MERGER_MERGE = 1 # "git merge"
25MERGER_MERGE_RESOLVE = 2 # "git merge -s resolve"
26MERGER_CHERRY_PICK = 3 # "git cherry-pick"
27
28MERGER_MAP = {
29 'merge': MERGER_MERGE,
30 'merge-resolve': MERGER_MERGE_RESOLVE,
31 'cherry-pick': MERGER_CHERRY_PICK,
32}
James E. Blairee743612012-05-29 14:49:32 -070033
James E. Blair64ed6f22013-07-10 14:07:23 -070034PRECEDENCE_NORMAL = 0
35PRECEDENCE_LOW = 1
36PRECEDENCE_HIGH = 2
37
38PRECEDENCE_MAP = {
39 None: PRECEDENCE_NORMAL,
40 'low': PRECEDENCE_LOW,
41 'normal': PRECEDENCE_NORMAL,
42 'high': PRECEDENCE_HIGH,
43}
44
James E. Blair1e8dd892012-05-30 09:15:05 -070045
James E. Blairc053d022014-01-22 14:57:33 -080046def time_to_seconds(s):
47 if s.endswith('s'):
48 return int(s[:-1])
49 if s.endswith('m'):
50 return int(s[:-1]) * 60
51 if s.endswith('h'):
52 return int(s[:-1]) * 60 * 60
53 if s.endswith('d'):
54 return int(s[:-1]) * 24 * 60 * 60
55 if s.endswith('w'):
56 return int(s[:-1]) * 7 * 24 * 60 * 60
57 raise Exception("Unable to parse time value: %s" % s)
58
59
James E. Blair4aea70c2012-07-26 14:23:24 -070060class Pipeline(object):
61 """A top-level pipeline such as check, gate, post, etc."""
62 def __init__(self, name):
63 self.name = name
James E. Blair8dbd56a2012-12-22 10:55:10 -080064 self.description = None
James E. Blair56370192013-01-14 15:47:28 -080065 self.failure_message = None
Joshua Heskethb7179772014-01-30 23:30:46 +110066 self.merge_failure_message = None
James E. Blair56370192013-01-14 15:47:28 -080067 self.success_message = None
Joshua Hesketh3979e3e2014-03-04 11:21:10 +110068 self.footer_message = None
James E. Blair2fa50962013-01-30 21:50:41 -080069 self.dequeue_on_new_patchset = True
James E. Blair4aea70c2012-07-26 14:23:24 -070070 self.job_trees = {} # project -> JobTree
71 self.manager = None
James E. Blaire0487072012-08-29 17:38:31 -070072 self.queues = []
James E. Blair64ed6f22013-07-10 14:07:23 -070073 self.precedence = PRECEDENCE_NORMAL
James E. Blair6c358e72013-07-29 17:06:47 -070074 self.trigger = None
Joshua Hesketh1879cf72013-08-19 14:13:15 +100075 self.start_actions = None
76 self.success_actions = None
77 self.failure_actions = None
Clark Boylan7603a372014-01-21 11:43:20 -080078 self.window = None
79 self.window_floor = None
80 self.window_increase_type = None
81 self.window_increase_factor = None
82 self.window_decrease_type = None
83 self.window_decrease_factor = None
James E. Blair4aea70c2012-07-26 14:23:24 -070084
James E. Blaird09c17a2012-08-07 09:23:14 -070085 def __repr__(self):
86 return '<Pipeline %s>' % self.name
87
James E. Blair4aea70c2012-07-26 14:23:24 -070088 def setManager(self, manager):
89 self.manager = manager
90
91 def addProject(self, project):
92 job_tree = JobTree(None) # Null job == job tree root
93 self.job_trees[project] = job_tree
94 return job_tree
95
96 def getProjects(self):
James E. Blairc3d428e2013-12-03 15:06:48 -080097 return sorted(self.job_trees.keys(), lambda a, b: cmp(a.name, b.name))
James E. Blair4aea70c2012-07-26 14:23:24 -070098
James E. Blaire0487072012-08-29 17:38:31 -070099 def addQueue(self, queue):
100 self.queues.append(queue)
101
102 def getQueue(self, project):
103 for queue in self.queues:
104 if project in queue.projects:
105 return queue
106 return None
107
James E. Blair4aea70c2012-07-26 14:23:24 -0700108 def getJobTree(self, project):
109 tree = self.job_trees.get(project)
110 return tree
111
112 def getJobs(self, changeish):
James E. Blaird09c17a2012-08-07 09:23:14 -0700113 tree = self.getJobTree(changeish.project)
James E. Blair4aea70c2012-07-26 14:23:24 -0700114 if not tree:
115 return []
116 return changeish.filterJobs(tree.getJobs())
117
James E. Blairfee8d652013-06-07 08:57:52 -0700118 def _findJobsToRun(self, job_trees, item):
James E. Blair4aea70c2012-07-26 14:23:24 -0700119 torun = []
James E. Blairfee8d652013-06-07 08:57:52 -0700120 if item.item_ahead:
James E. Blair4aea70c2012-07-26 14:23:24 -0700121 # Only run jobs if any 'hold' jobs on the change ahead
122 # have completed successfully.
James E. Blairfee8d652013-06-07 08:57:52 -0700123 if self.isHoldingFollowingChanges(item.item_ahead):
James E. Blair4aea70c2012-07-26 14:23:24 -0700124 return []
125 for tree in job_trees:
126 job = tree.job
127 result = None
128 if job:
James E. Blairfee8d652013-06-07 08:57:52 -0700129 if not job.changeMatches(item.change):
James E. Blair4aea70c2012-07-26 14:23:24 -0700130 continue
James E. Blairfee8d652013-06-07 08:57:52 -0700131 build = item.current_build_set.getBuild(job.name)
James E. Blair4aea70c2012-07-26 14:23:24 -0700132 if build:
133 result = build.result
134 else:
135 # There is no build for the root of this job tree,
136 # so we should run it.
137 torun.append(job)
138 # If there is no job, this is a null job tree, and we should
139 # run all of its jobs.
140 if result == 'SUCCESS' or not job:
James E. Blairfee8d652013-06-07 08:57:52 -0700141 torun.extend(self._findJobsToRun(tree.job_trees, item))
James E. Blair4aea70c2012-07-26 14:23:24 -0700142 return torun
143
James E. Blairfee8d652013-06-07 08:57:52 -0700144 def findJobsToRun(self, item):
145 tree = self.getJobTree(item.change.project)
James E. Blair4aea70c2012-07-26 14:23:24 -0700146 if not tree:
147 return []
James E. Blairfee8d652013-06-07 08:57:52 -0700148 return self._findJobsToRun(tree.job_trees, item)
James E. Blair4aea70c2012-07-26 14:23:24 -0700149
James E. Blairbea9ef12013-07-15 11:52:23 -0700150 def haveAllJobsStarted(self, item):
151 for job in self.getJobs(item.change):
152 build = item.current_build_set.getBuild(job.name)
153 if not build or not build.start_time:
154 return False
155 return True
156
James E. Blairfee8d652013-06-07 08:57:52 -0700157 def areAllJobsComplete(self, item):
158 for job in self.getJobs(item.change):
159 build = item.current_build_set.getBuild(job.name)
James E. Blair4aea70c2012-07-26 14:23:24 -0700160 if not build or not build.result:
161 return False
162 return True
163
James E. Blairfee8d652013-06-07 08:57:52 -0700164 def didAllJobsSucceed(self, item):
165 for job in self.getJobs(item.change):
James E. Blair4ec821f2012-08-23 15:28:28 -0700166 if not job.voting:
167 continue
James E. Blairfee8d652013-06-07 08:57:52 -0700168 build = item.current_build_set.getBuild(job.name)
James E. Blair4aea70c2012-07-26 14:23:24 -0700169 if not build:
170 return False
171 if build.result != 'SUCCESS':
172 return False
173 return True
174
Joshua Heskethb7179772014-01-30 23:30:46 +1100175 def didMergerSucceed(self, item):
176 if item.current_build_set.unable_to_merge:
177 return False
178 return True
179
James E. Blairfee8d652013-06-07 08:57:52 -0700180 def didAnyJobFail(self, item):
181 for job in self.getJobs(item.change):
James E. Blair4ec821f2012-08-23 15:28:28 -0700182 if not job.voting:
183 continue
James E. Blairfee8d652013-06-07 08:57:52 -0700184 build = item.current_build_set.getBuild(job.name)
James E. Blair0018a6c2013-02-27 14:11:45 -0800185 if build and build.result and (build.result != 'SUCCESS'):
James E. Blair4aea70c2012-07-26 14:23:24 -0700186 return True
187 return False
188
James E. Blairfee8d652013-06-07 08:57:52 -0700189 def isHoldingFollowingChanges(self, item):
190 for job in self.getJobs(item.change):
James E. Blair4aea70c2012-07-26 14:23:24 -0700191 if not job.hold_following_changes:
192 continue
James E. Blairfee8d652013-06-07 08:57:52 -0700193 build = item.current_build_set.getBuild(job.name)
James E. Blair4aea70c2012-07-26 14:23:24 -0700194 if not build:
195 return True
196 if build.result != 'SUCCESS':
197 return True
James E. Blair972e3c72013-08-29 12:04:55 -0700198
James E. Blairfee8d652013-06-07 08:57:52 -0700199 if not item.item_ahead:
James E. Blair4aea70c2012-07-26 14:23:24 -0700200 return False
James E. Blairfee8d652013-06-07 08:57:52 -0700201 return self.isHoldingFollowingChanges(item.item_ahead)
James E. Blair4aea70c2012-07-26 14:23:24 -0700202
James E. Blairfee8d652013-06-07 08:57:52 -0700203 def setResult(self, item, build):
James E. Blair4a28a882013-08-23 15:17:33 -0700204 if build.retry:
205 item.removeBuild(build)
206 elif build.result != 'SUCCESS':
James E. Blair4aea70c2012-07-26 14:23:24 -0700207 # Get a JobTree from a Job so we can find only its dependent jobs
James E. Blairfee8d652013-06-07 08:57:52 -0700208 root = self.getJobTree(item.change.project)
James E. Blair4aea70c2012-07-26 14:23:24 -0700209 tree = root.getJobTreeForJob(build.job)
210 for job in tree.getJobs():
211 fakebuild = Build(job, None)
212 fakebuild.result = 'SKIPPED'
James E. Blairfee8d652013-06-07 08:57:52 -0700213 item.addBuild(fakebuild)
James E. Blair4aea70c2012-07-26 14:23:24 -0700214
Joshua Heskethb7179772014-01-30 23:30:46 +1100215 def setUnableToMerge(self, item):
James E. Blairfee8d652013-06-07 08:57:52 -0700216 item.current_build_set.unable_to_merge = True
217 root = self.getJobTree(item.change.project)
James E. Blair973721f2012-08-15 10:19:43 -0700218 for job in root.getJobs():
219 fakebuild = Build(job, None)
220 fakebuild.result = 'SKIPPED'
James E. Blairfee8d652013-06-07 08:57:52 -0700221 item.addBuild(fakebuild)
James E. Blair973721f2012-08-15 10:19:43 -0700222
James E. Blairfee8d652013-06-07 08:57:52 -0700223 def setDequeuedNeedingChange(self, item):
224 item.dequeued_needing_change = True
225 root = self.getJobTree(item.change.project)
James E. Blaircaec0c52012-08-22 14:52:22 -0700226 for job in root.getJobs():
227 fakebuild = Build(job, None)
228 fakebuild.result = 'SKIPPED'
James E. Blairfee8d652013-06-07 08:57:52 -0700229 item.addBuild(fakebuild)
James E. Blaircaec0c52012-08-22 14:52:22 -0700230
James E. Blaire0487072012-08-29 17:38:31 -0700231 def getChangesInQueue(self):
232 changes = []
233 for shared_queue in self.queues:
James E. Blairfee8d652013-06-07 08:57:52 -0700234 changes.extend([x.change for x in shared_queue.queue])
James E. Blaire0487072012-08-29 17:38:31 -0700235 return changes
236
James E. Blairfee8d652013-06-07 08:57:52 -0700237 def getAllItems(self):
238 items = []
James E. Blaire0487072012-08-29 17:38:31 -0700239 for shared_queue in self.queues:
James E. Blairfee8d652013-06-07 08:57:52 -0700240 items.extend(shared_queue.queue)
James E. Blairfee8d652013-06-07 08:57:52 -0700241 return items
James E. Blaire0487072012-08-29 17:38:31 -0700242
243 def formatStatusHTML(self):
244 ret = ''
245 for queue in self.queues:
246 if len(self.queues) > 1:
247 s = 'Change queue: %s' % queue.name
248 ret += s + '\n'
249 ret += '-' * len(s) + '\n'
James E. Blair972e3c72013-08-29 12:04:55 -0700250 for item in queue.queue:
251 ret += self.formatStatus(item, html=True)
James E. Blaire0487072012-08-29 17:38:31 -0700252 return ret
253
James E. Blair8dbd56a2012-12-22 10:55:10 -0800254 def formatStatusJSON(self):
255 j_pipeline = dict(name=self.name,
256 description=self.description)
257 j_queues = []
258 j_pipeline['change_queues'] = j_queues
259 for queue in self.queues:
260 j_queue = dict(name=queue.name)
261 j_queues.append(j_queue)
262 j_queue['heads'] = []
Clark Boylanaf2476f2014-01-23 14:47:36 -0800263 j_queue['window'] = queue.window
264 j_queue['dependent'] = queue.dependent
James E. Blair972e3c72013-08-29 12:04:55 -0700265
266 j_changes = []
267 for e in queue.queue:
268 if not e.item_ahead:
269 if j_changes:
270 j_queue['heads'].append(j_changes)
271 j_changes = []
272 j_changes.append(self.formatItemJSON(e))
273 if (len(j_changes) > 1 and
274 (j_changes[-2]['remaining_time'] is not None) and
275 (j_changes[-1]['remaining_time'] is not None)):
276 j_changes[-1]['remaining_time'] = max(
277 j_changes[-2]['remaining_time'],
278 j_changes[-1]['remaining_time'])
279 if j_changes:
James E. Blair8dbd56a2012-12-22 10:55:10 -0800280 j_queue['heads'].append(j_changes)
281 return j_pipeline
282
James E. Blairfee8d652013-06-07 08:57:52 -0700283 def formatStatus(self, item, indent=0, html=False):
284 changeish = item.change
James E. Blaire0487072012-08-29 17:38:31 -0700285 indent_str = ' ' * indent
286 ret = ''
287 if html and hasattr(changeish, 'url') and changeish.url is not None:
288 ret += '%sProject %s change <a href="%s">%s</a>\n' % (
289 indent_str,
290 changeish.project.name,
291 changeish.url,
292 changeish._id())
293 else:
James E. Blair972e3c72013-08-29 12:04:55 -0700294 ret += '%sProject %s change %s based on %s\n' % (
295 indent_str,
296 changeish.project.name,
297 changeish._id(),
298 item.item_ahead)
James E. Blaire0487072012-08-29 17:38:31 -0700299 for job in self.getJobs(changeish):
James E. Blairfee8d652013-06-07 08:57:52 -0700300 build = item.current_build_set.getBuild(job.name)
James E. Blaire0487072012-08-29 17:38:31 -0700301 if build:
302 result = build.result
303 else:
304 result = None
305 job_name = job.name
306 if not job.voting:
307 voting = ' (non-voting)'
308 else:
309 voting = ''
310 if html:
311 if build:
312 url = build.url
313 else:
314 url = None
315 if url is not None:
316 job_name = '<a href="%s">%s</a>' % (url, job_name)
317 ret += '%s %s: %s%s' % (indent_str, job_name, result, voting)
318 ret += '\n'
James E. Blaire0487072012-08-29 17:38:31 -0700319 return ret
320
James E. Blairfee8d652013-06-07 08:57:52 -0700321 def formatItemJSON(self, item):
322 changeish = item.change
James E. Blair8dbd56a2012-12-22 10:55:10 -0800323 ret = {}
Clark Boylanaf2476f2014-01-23 14:47:36 -0800324 ret['active'] = item.active
James E. Blair8dbd56a2012-12-22 10:55:10 -0800325 if hasattr(changeish, 'url') and changeish.url is not None:
326 ret['url'] = changeish.url
James E. Blairc44b1382012-12-23 09:39:55 -0800327 else:
328 ret['url'] = None
James E. Blair8dbd56a2012-12-22 10:55:10 -0800329 ret['id'] = changeish._id()
James E. Blair2feda2d2013-09-13 11:48:19 -0700330 if item.item_ahead:
331 ret['item_ahead'] = item.item_ahead.change._id()
332 else:
333 ret['item_ahead'] = None
334 ret['items_behind'] = [i.change._id() for i in item.items_behind]
335 ret['failing_reasons'] = item.current_build_set.failing_reasons
James E. Blair062c4fb2013-09-26 07:46:00 -0700336 ret['zuul_ref'] = item.current_build_set.ref
James E. Blair8dbd56a2012-12-22 10:55:10 -0800337 ret['project'] = changeish.project.name
James E. Blairfee8d652013-06-07 08:57:52 -0700338 ret['enqueue_time'] = int(item.enqueue_time * 1000)
James E. Blair8dbd56a2012-12-22 10:55:10 -0800339 ret['jobs'] = []
James E. Blairbea9ef12013-07-15 11:52:23 -0700340 max_remaining = 0
James E. Blair8dbd56a2012-12-22 10:55:10 -0800341 for job in self.getJobs(changeish):
James E. Blairbea9ef12013-07-15 11:52:23 -0700342 now = time.time()
James E. Blairfee8d652013-06-07 08:57:52 -0700343 build = item.current_build_set.getBuild(job.name)
James E. Blairbea9ef12013-07-15 11:52:23 -0700344 elapsed = None
345 remaining = None
346 result = None
347 url = None
James E. Blair8dbd56a2012-12-22 10:55:10 -0800348 if build:
349 result = build.result
350 url = build.url
James E. Blairbea9ef12013-07-15 11:52:23 -0700351 if build.start_time:
352 if build.end_time:
353 elapsed = int((build.end_time -
354 build.start_time) * 1000)
355 remaining = 0
356 else:
357 elapsed = int((now - build.start_time) * 1000)
358 if build.estimated_time:
359 remaining = max(
360 int(build.estimated_time * 1000) - elapsed,
361 0)
362 if remaining and remaining > max_remaining:
363 max_remaining = remaining
James E. Blair8dbd56a2012-12-22 10:55:10 -0800364 ret['jobs'].append(
365 dict(
366 name=job.name,
James E. Blairbea9ef12013-07-15 11:52:23 -0700367 elapsed_time=elapsed,
368 remaining_time=remaining,
James E. Blair8dbd56a2012-12-22 10:55:10 -0800369 url=url,
370 result=result,
371 voting=job.voting))
James E. Blairbea9ef12013-07-15 11:52:23 -0700372 if self.haveAllJobsStarted(item):
James E. Blair972e3c72013-08-29 12:04:55 -0700373 ret['remaining_time'] = max_remaining
James E. Blairbea9ef12013-07-15 11:52:23 -0700374 else:
375 ret['remaining_time'] = None
James E. Blair8dbd56a2012-12-22 10:55:10 -0800376 return ret
377
James E. Blair4aea70c2012-07-26 14:23:24 -0700378
Joshua Hesketh1879cf72013-08-19 14:13:15 +1000379class ActionReporter(object):
380 """An ActionReporter has a reporter and its configured paramaters"""
381
382 def __repr__(self):
383 return '<ActionReporter %s, %s>' % (self.reporter, self.params)
384
385 def __init__(self, reporter, params):
386 self.reporter = reporter
387 self.params = params
388
389 def report(self, change, message):
390 """Sends the built message off to the configured reporter.
391 Takes the change and message and adds the configured parameters.
392 """
393 return self.reporter.report(change, message, self.params)
394
395 def getSubmitAllowNeeds(self):
396 """Gets the submit allow needs from the reporter based off the
397 parameters."""
398 return self.reporter.getSubmitAllowNeeds(self.params)
399
400
James E. Blairee743612012-05-29 14:49:32 -0700401class ChangeQueue(object):
James E. Blair4aea70c2012-07-26 14:23:24 -0700402 """DependentPipelines have multiple parallel queues shared by
403 different projects; this is one of them. For instance, there may
404 a queue shared by interrelated projects foo and bar, and a second
405 queue for independent project baz. Pipelines have one or more
406 PipelineQueues."""
Clark Boylan7603a372014-01-21 11:43:20 -0800407 def __init__(self, pipeline, dependent=True, window=0, window_floor=1,
408 window_increase_type='linear', window_increase_factor=1,
409 window_decrease_type='exponential', window_decrease_factor=2):
James E. Blair4aea70c2012-07-26 14:23:24 -0700410 self.pipeline = pipeline
James E. Blairee743612012-05-29 14:49:32 -0700411 self.name = ''
James E. Blairee743612012-05-29 14:49:32 -0700412 self.projects = []
413 self._jobs = set()
414 self.queue = []
James E. Blaire0487072012-08-29 17:38:31 -0700415 self.dependent = dependent
Clark Boylan7603a372014-01-21 11:43:20 -0800416 self.window = window
417 self.window_floor = window_floor
418 self.window_increase_type = window_increase_type
419 self.window_increase_factor = window_increase_factor
420 self.window_decrease_type = window_decrease_type
421 self.window_decrease_factor = window_decrease_factor
James E. Blairee743612012-05-29 14:49:32 -0700422
James E. Blair9f9667e2012-06-12 17:51:08 -0700423 def __repr__(self):
James E. Blair4aea70c2012-07-26 14:23:24 -0700424 return '<ChangeQueue %s: %s>' % (self.pipeline.name, self.name)
James E. Blairee743612012-05-29 14:49:32 -0700425
426 def getJobs(self):
427 return self._jobs
428
429 def addProject(self, project):
430 if project not in self.projects:
431 self.projects.append(project)
432 names = [x.name for x in self.projects]
433 names.sort()
434 self.name = ', '.join(names)
James E. Blair4aea70c2012-07-26 14:23:24 -0700435 self._jobs |= set(self.pipeline.getJobTree(project).getJobs())
James E. Blairee743612012-05-29 14:49:32 -0700436
437 def enqueueChange(self, change):
James E. Blair4a035d92014-01-23 13:10:48 -0800438 item = QueueItem(self.pipeline, change)
James E. Blaircdccd972013-07-01 12:10:22 -0700439 self.enqueueItem(item)
440 item.enqueue_time = time.time()
441 return item
442
443 def enqueueItem(self, item):
James E. Blair4a035d92014-01-23 13:10:48 -0800444 item.pipeline = self.pipeline
James E. Blair75241582012-08-31 12:16:55 -0700445 if self.dependent and self.queue:
James E. Blairfee8d652013-06-07 08:57:52 -0700446 item.item_ahead = self.queue[-1]
James E. Blair972e3c72013-08-29 12:04:55 -0700447 item.item_ahead.items_behind.append(item)
James E. Blairfee8d652013-06-07 08:57:52 -0700448 self.queue.append(item)
James E. Blairee743612012-05-29 14:49:32 -0700449
James E. Blairfee8d652013-06-07 08:57:52 -0700450 def dequeueItem(self, item):
451 if item in self.queue:
452 self.queue.remove(item)
James E. Blairfee8d652013-06-07 08:57:52 -0700453 if item.item_ahead:
James E. Blair972e3c72013-08-29 12:04:55 -0700454 item.item_ahead.items_behind.remove(item)
455 for item_behind in item.items_behind:
456 if item.item_ahead:
457 item.item_ahead.items_behind.append(item_behind)
458 item_behind.item_ahead = item.item_ahead
James E. Blairfee8d652013-06-07 08:57:52 -0700459 item.item_ahead = None
James E. Blair972e3c72013-08-29 12:04:55 -0700460 item.items_behind = []
James E. Blairfee8d652013-06-07 08:57:52 -0700461 item.dequeue_time = time.time()
James E. Blaire0487072012-08-29 17:38:31 -0700462
James E. Blair972e3c72013-08-29 12:04:55 -0700463 def moveItem(self, item, item_ahead):
464 if not self.dependent:
465 return False
466 if item.item_ahead == item_ahead:
467 return False
468 # Remove from current location
469 if item.item_ahead:
470 item.item_ahead.items_behind.remove(item)
471 for item_behind in item.items_behind:
472 if item.item_ahead:
473 item.item_ahead.items_behind.append(item_behind)
474 item_behind.item_ahead = item.item_ahead
475 # Add to new location
476 item.item_ahead = item_ahead
James E. Blair00451262013-09-20 11:40:17 -0700477 item.items_behind = []
James E. Blair972e3c72013-08-29 12:04:55 -0700478 if item.item_ahead:
479 item.item_ahead.items_behind.append(item)
480 return True
James E. Blairee743612012-05-29 14:49:32 -0700481
482 def mergeChangeQueue(self, other):
483 for project in other.projects:
484 self.addProject(project)
Clark Boylan7603a372014-01-21 11:43:20 -0800485 self.window = min(self.window, other.window)
486 # TODO merge semantics
487
Clark Boylan3d2f7a72014-01-23 11:07:42 -0800488 def isActionable(self, item):
Clark Boylan7603a372014-01-21 11:43:20 -0800489 if self.dependent and self.window:
Clark Boylan3d2f7a72014-01-23 11:07:42 -0800490 return item in self.queue[:self.window]
Clark Boylan7603a372014-01-21 11:43:20 -0800491 else:
Clark Boylan3d2f7a72014-01-23 11:07:42 -0800492 return True
Clark Boylan7603a372014-01-21 11:43:20 -0800493
494 def increaseWindowSize(self):
495 if self.dependent:
496 if self.window_increase_type == 'linear':
497 self.window += self.window_increase_factor
498 elif self.window_increase_type == 'exponential':
499 self.window *= self.window_increase_factor
500
501 def decreaseWindowSize(self):
502 if self.dependent:
503 if self.window_decrease_type == 'linear':
504 self.window = max(
505 self.window_floor,
506 self.window - self.window_decrease_factor)
507 elif self.window_decrease_type == 'exponential':
508 self.window = max(
509 self.window_floor,
510 self.window / self.window_decrease_factor)
James E. Blairee743612012-05-29 14:49:32 -0700511
James E. Blair1e8dd892012-05-30 09:15:05 -0700512
James E. Blair4aea70c2012-07-26 14:23:24 -0700513class Project(object):
514 def __init__(self, name):
515 self.name = name
James E. Blair19deff22013-08-25 13:17:35 -0700516 self.merge_mode = MERGER_MERGE_RESOLVE
James E. Blair4aea70c2012-07-26 14:23:24 -0700517
518 def __str__(self):
519 return self.name
520
521 def __repr__(self):
522 return '<Project %s>' % (self.name)
523
524
James E. Blairee743612012-05-29 14:49:32 -0700525class Job(object):
526 def __init__(self, name):
James E. Blair222d4982012-07-16 09:31:19 -0700527 # If you add attributes here, be sure to add them to the copy method.
James E. Blairee743612012-05-29 14:49:32 -0700528 self.name = name
529 self.failure_message = None
530 self.success_message = None
James E. Blair6aea36d2012-12-17 13:03:24 -0800531 self.failure_pattern = None
532 self.success_pattern = None
James E. Blaire5a847f2012-07-10 15:29:14 -0700533 self.parameter_function = None
James E. Blair222d4982012-07-16 09:31:19 -0700534 self.hold_following_changes = False
James E. Blair4ec821f2012-08-23 15:28:28 -0700535 self.voting = True
James E. Blaire421a232012-07-25 16:59:21 -0700536 self.branches = []
537 self._branches = []
James E. Blair70c71582013-03-06 08:50:50 -0800538 self.files = []
539 self._files = []
James E. Blairee743612012-05-29 14:49:32 -0700540
541 def __str__(self):
542 return self.name
543
544 def __repr__(self):
545 return '<Job %s>' % (self.name)
546
James E. Blairb0954652012-06-01 11:32:01 -0700547 def copy(self, other):
James E. Blairc28d1b02013-07-19 11:37:06 -0700548 if other.failure_message:
549 self.failure_message = other.failure_message
550 if other.success_message:
551 self.success_message = other.success_message
552 if other.failure_pattern:
553 self.failure_pattern = other.failure_pattern
554 if other.success_pattern:
555 self.success_pattern = other.success_pattern
556 if other.parameter_function:
557 self.parameter_function = other.parameter_function
558 if other.branches:
559 self.branches = other.branches[:]
560 self._branches = other._branches[:]
561 if other.files:
562 self.files = other.files[:]
563 self._files = other._files[:]
James E. Blair222d4982012-07-16 09:31:19 -0700564 self.hold_following_changes = other.hold_following_changes
James E. Blair4ec821f2012-08-23 15:28:28 -0700565 self.voting = other.voting
James E. Blairb0954652012-06-01 11:32:01 -0700566
James E. Blaire421a232012-07-25 16:59:21 -0700567 def changeMatches(self, change):
James E. Blair70c71582013-03-06 08:50:50 -0800568 matches_branch = False
James E. Blaire421a232012-07-25 16:59:21 -0700569 for branch in self.branches:
James E. Blair45865f32012-10-05 09:39:46 -0700570 if hasattr(change, 'branch') and branch.match(change.branch):
James E. Blair70c71582013-03-06 08:50:50 -0800571 matches_branch = True
James E. Blair45865f32012-10-05 09:39:46 -0700572 if hasattr(change, 'ref') and branch.match(change.ref):
James E. Blair70c71582013-03-06 08:50:50 -0800573 matches_branch = True
574 if self.branches and not matches_branch:
575 return False
576
577 matches_file = False
578 for f in self.files:
579 if hasattr(change, 'files'):
580 for cf in change.files:
581 if f.match(cf):
582 matches_file = True
583 if self.files and not matches_file:
584 return False
585
586 return True
James E. Blaire5a847f2012-07-10 15:29:14 -0700587
James E. Blair1e8dd892012-05-30 09:15:05 -0700588
James E. Blairee743612012-05-29 14:49:32 -0700589class JobTree(object):
590 """ A JobTree represents an instance of one Job, and holds JobTrees
591 whose jobs should be run if that Job succeeds. A root node of a
592 JobTree will have no associated Job. """
593
594 def __init__(self, job):
595 self.job = job
596 self.job_trees = []
597
598 def addJob(self, job):
James E. Blair3aa37272013-09-27 08:33:10 -0700599 t = JobTree(job)
600 self.job_trees.append(t)
601 return t
James E. Blairee743612012-05-29 14:49:32 -0700602
603 def getJobs(self):
604 jobs = []
605 for x in self.job_trees:
606 jobs.append(x.job)
607 jobs.extend(x.getJobs())
608 return jobs
609
610 def getJobTreeForJob(self, job):
611 if self.job == job:
612 return self
613 for tree in self.job_trees:
614 ret = tree.getJobTreeForJob(job)
615 if ret:
616 return ret
617 return None
618
James E. Blair1e8dd892012-05-30 09:15:05 -0700619
James E. Blair4aea70c2012-07-26 14:23:24 -0700620class Build(object):
621 def __init__(self, job, uuid):
622 self.job = job
623 self.uuid = uuid
James E. Blair4aea70c2012-07-26 14:23:24 -0700624 self.url = None
625 self.number = None
626 self.result = None
627 self.build_set = None
628 self.launch_time = time.time()
James E. Blair71e94122012-12-24 17:53:08 -0800629 self.start_time = None
630 self.end_time = None
James E. Blairbea9ef12013-07-15 11:52:23 -0700631 self.estimated_time = None
James E. Blair66eeebf2013-07-27 17:44:32 -0700632 self.pipeline = None
James E. Blair0aac4872013-08-23 14:02:38 -0700633 self.canceled = False
James E. Blair4a28a882013-08-23 15:17:33 -0700634 self.retry = False
James E. Blaird78576a2013-07-09 10:39:17 -0700635 self.parameters = {}
Joshua Heskethba8776a2014-01-12 14:35:40 +0800636 self.worker = Worker()
James E. Blairee743612012-05-29 14:49:32 -0700637
638 def __repr__(self):
Joshua Heskethba8776a2014-01-12 14:35:40 +0800639 return ('<Build %s of %s on %s>' %
640 (self.uuid, self.job.name, self.worker))
641
642
643class Worker(object):
644 """A model of the worker running a job"""
645 def __init__(self):
646 self.name = "Unknown"
647 self.hostname = None
648 self.ips = []
649 self.fqdn = None
650 self.program = None
651 self.version = None
652 self.extra = {}
653
654 def updateFromData(self, data):
655 """Update worker information if contained in the WORK_DATA response."""
656 self.name = data.get('worker_name', self.name)
657 self.hostname = data.get('worker_hostname', self.hostname)
658 self.ips = data.get('worker_ips', self.ips)
659 self.fqdn = data.get('worker_fqdn', self.fqdn)
660 self.program = data.get('worker_program', self.program)
661 self.version = data.get('worker_version', self.version)
662 self.extra = data.get('worker_extra', self.extra)
663
664 def __repr__(self):
665 return '<Worker %s>' % self.name
James E. Blairee743612012-05-29 14:49:32 -0700666
James E. Blair1e8dd892012-05-30 09:15:05 -0700667
James E. Blair7e530ad2012-07-03 16:12:28 -0700668class BuildSet(object):
James E. Blair4076e2b2014-01-28 12:42:20 -0800669 # Merge states:
670 NEW = 1
671 PENDING = 2
672 COMPLETE = 3
673
James E. Blairfee8d652013-06-07 08:57:52 -0700674 def __init__(self, item):
675 self.item = item
James E. Blair11700c32012-07-05 17:50:05 -0700676 self.other_changes = []
James E. Blair7e530ad2012-07-03 16:12:28 -0700677 self.builds = {}
James E. Blair11700c32012-07-05 17:50:05 -0700678 self.result = None
679 self.next_build_set = None
680 self.previous_build_set = None
James E. Blair4886cc12012-07-18 15:39:41 -0700681 self.ref = None
James E. Blair81515ad2012-10-01 18:29:08 -0700682 self.commit = None
James E. Blair4076e2b2014-01-28 12:42:20 -0800683 self.zuul_url = None
James E. Blair973721f2012-08-15 10:19:43 -0700684 self.unable_to_merge = False
James E. Blair972e3c72013-08-29 12:04:55 -0700685 self.failing_reasons = []
James E. Blair4076e2b2014-01-28 12:42:20 -0800686 self.merge_state = self.NEW
James E. Blair7e530ad2012-07-03 16:12:28 -0700687
James E. Blair4886cc12012-07-18 15:39:41 -0700688 def setConfiguration(self):
James E. Blair11700c32012-07-05 17:50:05 -0700689 # The change isn't enqueued until after it's created
690 # so we don't know what the other changes ahead will be
691 # until jobs start.
692 if not self.other_changes:
James E. Blairfee8d652013-06-07 08:57:52 -0700693 next_item = self.item.item_ahead
694 while next_item:
695 self.other_changes.append(next_item.change)
696 next_item = next_item.item_ahead
James E. Blair4886cc12012-07-18 15:39:41 -0700697 if not self.ref:
698 self.ref = 'Z' + uuid4().hex
699
James E. Blair4886cc12012-07-18 15:39:41 -0700700 def addBuild(self, build):
701 self.builds[build.job.name] = build
702 build.build_set = self
James E. Blair11700c32012-07-05 17:50:05 -0700703
James E. Blair4a28a882013-08-23 15:17:33 -0700704 def removeBuild(self, build):
705 del self.builds[build.job.name]
706
James E. Blair7e530ad2012-07-03 16:12:28 -0700707 def getBuild(self, job_name):
708 return self.builds.get(job_name)
709
James E. Blair11700c32012-07-05 17:50:05 -0700710 def getBuilds(self):
711 keys = self.builds.keys()
712 keys.sort()
713 return [self.builds.get(x) for x in keys]
714
James E. Blair7e530ad2012-07-03 16:12:28 -0700715
James E. Blairfee8d652013-06-07 08:57:52 -0700716class QueueItem(object):
717 """A changish inside of a Pipeline queue"""
James E. Blair32663402012-06-01 10:04:18 -0700718
James E. Blair4a035d92014-01-23 13:10:48 -0800719 def __init__(self, pipeline, change):
James E. Blairfee8d652013-06-07 08:57:52 -0700720 self.pipeline = pipeline
721 self.change = change # a changeish
James E. Blair7e530ad2012-07-03 16:12:28 -0700722 self.build_sets = []
James E. Blaircaec0c52012-08-22 14:52:22 -0700723 self.dequeued_needing_change = False
James E. Blair11700c32012-07-05 17:50:05 -0700724 self.current_build_set = BuildSet(self)
725 self.build_sets.append(self.current_build_set)
James E. Blairfee8d652013-06-07 08:57:52 -0700726 self.item_ahead = None
James E. Blair972e3c72013-08-29 12:04:55 -0700727 self.items_behind = []
James E. Blair8fa16972013-01-15 16:57:20 -0800728 self.enqueue_time = None
729 self.dequeue_time = None
James E. Blairfee8d652013-06-07 08:57:52 -0700730 self.reported = False
Clark Boylanaf2476f2014-01-23 14:47:36 -0800731 self.active = False
James E. Blaire5a847f2012-07-10 15:29:14 -0700732
James E. Blair972e3c72013-08-29 12:04:55 -0700733 def __repr__(self):
734 if self.pipeline:
735 pipeline = self.pipeline.name
736 else:
737 pipeline = None
738 return '<QueueItem 0x%x for %s in %s>' % (
739 id(self), self.change, pipeline)
740
James E. Blairee743612012-05-29 14:49:32 -0700741 def resetAllBuilds(self):
James E. Blair11700c32012-07-05 17:50:05 -0700742 old = self.current_build_set
743 self.current_build_set.result = 'CANCELED'
744 self.current_build_set = BuildSet(self)
745 old.next_build_set = self.current_build_set
746 self.current_build_set.previous_build_set = old
James E. Blair7e530ad2012-07-03 16:12:28 -0700747 self.build_sets.append(self.current_build_set)
James E. Blairee743612012-05-29 14:49:32 -0700748
749 def addBuild(self, build):
James E. Blair7e530ad2012-07-03 16:12:28 -0700750 self.current_build_set.addBuild(build)
James E. Blair66eeebf2013-07-27 17:44:32 -0700751 build.pipeline = self.pipeline
James E. Blairee743612012-05-29 14:49:32 -0700752
James E. Blair4a28a882013-08-23 15:17:33 -0700753 def removeBuild(self, build):
754 self.current_build_set.removeBuild(build)
755
James E. Blairfee8d652013-06-07 08:57:52 -0700756 def setReportedResult(self, result):
757 self.current_build_set.result = result
758
759
760class Changeish(object):
761 """Something like a change; either a change or a ref"""
James E. Blairfee8d652013-06-07 08:57:52 -0700762
763 def __init__(self, project):
764 self.project = project
765
766 def equals(self, other):
767 raise NotImplementedError()
768
769 def isUpdateOf(self, other):
770 raise NotImplementedError()
771
772 def filterJobs(self, jobs):
773 return filter(lambda job: job.changeMatches(self), jobs)
774
775 def getRelatedChanges(self):
776 return set()
777
James E. Blair1e8dd892012-05-30 09:15:05 -0700778
James E. Blair4aea70c2012-07-26 14:23:24 -0700779class Change(Changeish):
James E. Blair4aea70c2012-07-26 14:23:24 -0700780 def __init__(self, project):
781 super(Change, self).__init__(project)
782 self.branch = None
783 self.number = None
784 self.url = None
785 self.patchset = None
786 self.refspec = None
787
James E. Blair70c71582013-03-06 08:50:50 -0800788 self.files = []
James E. Blair4aea70c2012-07-26 14:23:24 -0700789 self.needs_change = None
790 self.needed_by_changes = []
791 self.is_current_patchset = True
792 self.can_merge = False
793 self.is_merged = False
James E. Blairfee8d652013-06-07 08:57:52 -0700794 self.failed_to_merge = False
James E. Blairc053d022014-01-22 14:57:33 -0800795 self.approvals = []
James E. Blair4aea70c2012-07-26 14:23:24 -0700796
797 def _id(self):
James E. Blairbe765db2012-08-07 08:36:20 -0700798 return '%s,%s' % (self.number, self.patchset)
James E. Blair4aea70c2012-07-26 14:23:24 -0700799
800 def __repr__(self):
801 return '<Change 0x%x %s>' % (id(self), self._id())
802
803 def equals(self, other):
Zhongyue Luoaa85ebf2012-09-21 16:38:33 +0800804 if self.number == other.number and self.patchset == other.patchset:
James E. Blair4aea70c2012-07-26 14:23:24 -0700805 return True
806 return False
807
James E. Blair2fa50962013-01-30 21:50:41 -0800808 def isUpdateOf(self, other):
Clark Boylan01976242013-02-17 18:41:48 -0800809 if ((hasattr(other, 'number') and self.number == other.number) and
James E. Blair7a192e42013-07-11 14:10:36 -0700810 (hasattr(other, 'patchset') and
811 self.patchset is not None and
812 other.patchset is not None and
813 int(self.patchset) > int(other.patchset))):
James E. Blair2fa50962013-01-30 21:50:41 -0800814 return True
815 return False
816
James E. Blairfee8d652013-06-07 08:57:52 -0700817 def getRelatedChanges(self):
818 related = set()
819 if self.needs_change:
820 related.add(self.needs_change)
821 for c in self.needed_by_changes:
822 related.add(c)
823 related.update(c.getRelatedChanges())
824 return related
James E. Blair4aea70c2012-07-26 14:23:24 -0700825
826
827class Ref(Changeish):
James E. Blair4aea70c2012-07-26 14:23:24 -0700828 def __init__(self, project):
James E. Blairbe765db2012-08-07 08:36:20 -0700829 super(Ref, self).__init__(project)
James E. Blair4aea70c2012-07-26 14:23:24 -0700830 self.ref = None
831 self.oldrev = None
832 self.newrev = None
833
James E. Blairbe765db2012-08-07 08:36:20 -0700834 def _id(self):
835 return self.newrev
836
Antoine Musso68bdcd72013-01-17 12:31:28 +0100837 def __repr__(self):
838 rep = None
839 if self.newrev == '0000000000000000000000000000000000000000':
840 rep = '<Ref 0x%x deletes %s from %s' % (
841 id(self), self.ref, self.oldrev)
842 elif self.oldrev == '0000000000000000000000000000000000000000':
843 rep = '<Ref 0x%x creates %s on %s>' % (
844 id(self), self.ref, self.newrev)
845 else:
846 # Catch all
847 rep = '<Ref 0x%x %s updated %s..%s>' % (
848 id(self), self.ref, self.oldrev, self.newrev)
849
850 return rep
851
James E. Blair4aea70c2012-07-26 14:23:24 -0700852 def equals(self, other):
James E. Blair9358c612012-09-28 08:29:39 -0700853 if (self.project == other.project
854 and self.ref == other.ref
855 and self.newrev == other.newrev):
James E. Blair4aea70c2012-07-26 14:23:24 -0700856 return True
857 return False
858
James E. Blair2fa50962013-01-30 21:50:41 -0800859 def isUpdateOf(self, other):
860 return False
861
James E. Blair4aea70c2012-07-26 14:23:24 -0700862
James E. Blair63bb0ef2013-07-29 17:14:51 -0700863class NullChange(Changeish):
James E. Blaire5910202013-12-27 09:50:31 -0800864 def __repr__(self):
865 return '<NullChange for %s>' % (self.project)
James E. Blair63bb0ef2013-07-29 17:14:51 -0700866
James E. Blair63bb0ef2013-07-29 17:14:51 -0700867 def _id(self):
Alex Gaynorddb9ef32013-09-16 21:04:58 -0700868 return None
James E. Blair63bb0ef2013-07-29 17:14:51 -0700869
870 def equals(self, other):
871 return False
872
873 def isUpdateOf(self, other):
874 return False
875
876
James E. Blairee743612012-05-29 14:49:32 -0700877class TriggerEvent(object):
878 def __init__(self):
879 self.data = None
James E. Blair32663402012-06-01 10:04:18 -0700880 # common
James E. Blairee743612012-05-29 14:49:32 -0700881 self.type = None
882 self.project_name = None
James E. Blair6c358e72013-07-29 17:06:47 -0700883 self.trigger_name = None
Antoine Mussob4e809e2012-12-06 16:58:06 +0100884 # Representation of the user account that performed the event.
885 self.account = None
James E. Blair32663402012-06-01 10:04:18 -0700886 # patchset-created, comment-added, etc.
James E. Blairee743612012-05-29 14:49:32 -0700887 self.change_number = None
Clark Boylanfc56df32012-06-28 15:25:57 -0700888 self.change_url = None
James E. Blairee743612012-05-29 14:49:32 -0700889 self.patch_number = None
James E. Blaira03262c2012-05-30 09:41:16 -0700890 self.refspec = None
James E. Blairee743612012-05-29 14:49:32 -0700891 self.approvals = []
892 self.branch = None
Clark Boylanb9bcb402012-06-29 17:44:05 -0700893 self.comment = None
James E. Blair32663402012-06-01 10:04:18 -0700894 # ref-updated
James E. Blairee743612012-05-29 14:49:32 -0700895 self.ref = None
James E. Blair32663402012-06-01 10:04:18 -0700896 self.oldrev = None
James E. Blair89cae0f2012-07-18 11:18:32 -0700897 self.newrev = None
James E. Blair63bb0ef2013-07-29 17:14:51 -0700898 # timer
899 self.timespec = None
James E. Blairad28e912013-11-27 10:43:22 -0800900 # For events that arrive with a destination pipeline (eg, from
901 # an admin command, etc):
902 self.forced_pipeline = None
James E. Blairee743612012-05-29 14:49:32 -0700903
James E. Blair9f9667e2012-06-12 17:51:08 -0700904 def __repr__(self):
James E. Blairee743612012-05-29 14:49:32 -0700905 ret = '<TriggerEvent %s %s' % (self.type, self.project_name)
James E. Blair1e8dd892012-05-30 09:15:05 -0700906
James E. Blairee743612012-05-29 14:49:32 -0700907 if self.branch:
908 ret += " %s" % self.branch
909 if self.change_number:
910 ret += " %s,%s" % (self.change_number, self.patch_number)
911 if self.approvals:
James E. Blair1e8dd892012-05-30 09:15:05 -0700912 ret += ' ' + ', '.join(
913 ['%s:%s' % (a['type'], a['value']) for a in self.approvals])
James E. Blairee743612012-05-29 14:49:32 -0700914 ret += '>'
915
916 return ret
917
James E. Blair4aea70c2012-07-26 14:23:24 -0700918 def getChange(self, project, trigger):
James E. Blaire421a232012-07-25 16:59:21 -0700919 if self.change_number:
James E. Blair4aea70c2012-07-26 14:23:24 -0700920 change = trigger.getChange(self.change_number, self.patch_number)
James E. Blair63bb0ef2013-07-29 17:14:51 -0700921 elif self.ref:
James E. Blair4aea70c2012-07-26 14:23:24 -0700922 change = Ref(project)
James E. Blaire421a232012-07-25 16:59:21 -0700923 change.ref = self.ref
924 change.oldrev = self.oldrev
925 change.newrev = self.newrev
James E. Blairc44b1382012-12-23 09:39:55 -0800926 change.url = trigger.getGitwebUrl(project, sha=self.newrev)
James E. Blair63bb0ef2013-07-29 17:14:51 -0700927 else:
928 change = NullChange(project)
James E. Blaire421a232012-07-25 16:59:21 -0700929
930 return change
931
James E. Blair1e8dd892012-05-30 09:15:05 -0700932
James E. Blairee743612012-05-29 14:49:32 -0700933class EventFilter(object):
James E. Blairc053d022014-01-22 14:57:33 -0800934 def __init__(self, types=[], branches=[], refs=[], event_approvals={},
Joshua Heskethb8a817e2013-12-27 11:21:38 +1100935 comment_filters=[], email_filters=[], username_filters=[],
James E. Blairc053d022014-01-22 14:57:33 -0800936 timespecs=[], require_approvals=[]):
James E. Blairee743612012-05-29 14:49:32 -0700937 self._types = types
938 self._branches = branches
939 self._refs = refs
James E. Blaircf429f32012-12-20 14:28:24 -0800940 self._comment_filters = comment_filters
941 self._email_filters = email_filters
Joshua Heskethb8a817e2013-12-27 11:21:38 +1100942 self._username_filters = username_filters
James E. Blairee743612012-05-29 14:49:32 -0700943 self.types = [re.compile(x) for x in types]
944 self.branches = [re.compile(x) for x in branches]
945 self.refs = [re.compile(x) for x in refs]
Clark Boylanb9bcb402012-06-29 17:44:05 -0700946 self.comment_filters = [re.compile(x) for x in comment_filters]
Antoine Mussob4e809e2012-12-06 16:58:06 +0100947 self.email_filters = [re.compile(x) for x in email_filters]
Joshua Heskethb8a817e2013-12-27 11:21:38 +1100948 self.username_filters = [re.compile(x) for x in username_filters]
James E. Blairc053d022014-01-22 14:57:33 -0800949 self.event_approvals = event_approvals
950 self.require_approvals = require_approvals
James E. Blair63bb0ef2013-07-29 17:14:51 -0700951 self.timespecs = timespecs
James E. Blairee743612012-05-29 14:49:32 -0700952
James E. Blairc053d022014-01-22 14:57:33 -0800953 for a in self.require_approvals:
954 if 'older-than' in a:
955 a['older-than'] = time_to_seconds(a['older-than'])
956 if 'newer-than' in a:
957 a['newer-than'] = time_to_seconds(a['newer-than'])
958 if 'email-filter' in a:
959 a['email-filter'] = re.compile(a['email-filter'])
960
James E. Blair9f9667e2012-06-12 17:51:08 -0700961 def __repr__(self):
James E. Blairee743612012-05-29 14:49:32 -0700962 ret = '<EventFilter'
James E. Blair1e8dd892012-05-30 09:15:05 -0700963
James E. Blairee743612012-05-29 14:49:32 -0700964 if self._types:
965 ret += ' types: %s' % ', '.join(self._types)
966 if self._branches:
967 ret += ' branches: %s' % ', '.join(self._branches)
968 if self._refs:
969 ret += ' refs: %s' % ', '.join(self._refs)
James E. Blairc053d022014-01-22 14:57:33 -0800970 if self.event_approvals:
971 ret += ' event_approvals: %s' % ', '.join(
972 ['%s:%s' % a for a in self.event_approvals.items()])
James E. Blaircf429f32012-12-20 14:28:24 -0800973 if self._comment_filters:
974 ret += ' comment_filters: %s' % ', '.join(self._comment_filters)
975 if self._email_filters:
976 ret += ' email_filters: %s' % ', '.join(self._email_filters)
Joshua Heskethb8a817e2013-12-27 11:21:38 +1100977 if self._username_filters:
978 ret += ' username_filters: %s' % ', '.join(self._username_filters)
James E. Blair63bb0ef2013-07-29 17:14:51 -0700979 if self.timespecs:
980 ret += ' timespecs: %s' % ', '.join(self.timespecs)
James E. Blairee743612012-05-29 14:49:32 -0700981 ret += '>'
982
983 return ret
984
James E. Blairc053d022014-01-22 14:57:33 -0800985 def matches(self, event, change):
James E. Blairee743612012-05-29 14:49:32 -0700986 def normalizeCategory(name):
987 name = name.lower()
988 return re.sub(' ', '-', name)
989
990 # event types are ORed
991 matches_type = False
992 for etype in self.types:
993 if etype.match(event.type):
994 matches_type = True
995 if self.types and not matches_type:
996 return False
997
998 # branches are ORed
999 matches_branch = False
1000 for branch in self.branches:
1001 if branch.match(event.branch):
1002 matches_branch = True
1003 if self.branches and not matches_branch:
1004 return False
1005
1006 # refs are ORed
1007 matches_ref = False
1008 for ref in self.refs:
1009 if ref.match(event.ref):
1010 matches_ref = True
1011 if self.refs and not matches_ref:
1012 return False
1013
Clark Boylanb9bcb402012-06-29 17:44:05 -07001014 # comment_filters are ORed
1015 matches_comment_filter = False
1016 for comment_filter in self.comment_filters:
1017 if (event.comment is not None and
James E. Blaircf429f32012-12-20 14:28:24 -08001018 comment_filter.search(event.comment)):
Clark Boylanb9bcb402012-06-29 17:44:05 -07001019 matches_comment_filter = True
1020 if self.comment_filters and not matches_comment_filter:
1021 return False
1022
Antoine Mussob4e809e2012-12-06 16:58:06 +01001023 # We better have an account provided by Gerrit to do
1024 # email filtering.
1025 if event.account is not None:
James E. Blaircf429f32012-12-20 14:28:24 -08001026 account_email = event.account.get('email')
Antoine Mussob4e809e2012-12-06 16:58:06 +01001027 # email_filters are ORed
1028 matches_email_filter = False
1029 for email_filter in self.email_filters:
Antoine Mussob4e809e2012-12-06 16:58:06 +01001030 if (account_email is not None and
James E. Blaircf429f32012-12-20 14:28:24 -08001031 email_filter.search(account_email)):
Antoine Mussob4e809e2012-12-06 16:58:06 +01001032 matches_email_filter = True
1033 if self.email_filters and not matches_email_filter:
1034 return False
1035
Joshua Heskethb8a817e2013-12-27 11:21:38 +11001036 # username_filters are ORed
1037 account_username = event.account.get('username')
1038 matches_username_filter = False
1039 for username_filter in self.username_filters:
1040 if (account_username is not None and
1041 username_filter.search(account_username)):
1042 matches_username_filter = True
1043 if self.username_filters and not matches_username_filter:
1044 return False
1045
James E. Blairee743612012-05-29 14:49:32 -07001046 # approvals are ANDed
James E. Blairc053d022014-01-22 14:57:33 -08001047 for category, value in self.event_approvals.items():
James E. Blairee743612012-05-29 14:49:32 -07001048 matches_approval = False
1049 for eapproval in event.approvals:
1050 if (normalizeCategory(eapproval['description']) == category and
1051 int(eapproval['value']) == int(value)):
1052 matches_approval = True
James E. Blair1e8dd892012-05-30 09:15:05 -07001053 if not matches_approval:
1054 return False
James E. Blair63bb0ef2013-07-29 17:14:51 -07001055
James E. Blairc053d022014-01-22 14:57:33 -08001056 if self.require_approvals and not change.approvals:
1057 # A change with no approvals can not match
1058 return False
1059
1060 now = time.time()
1061 for rapproval in self.require_approvals:
1062 matches_approval = False
1063 for approval in change.approvals:
James E. Blairf74f1602014-02-26 09:59:43 -08001064 if 'description' not in approval:
1065 continue
James E. Blairc053d022014-01-22 14:57:33 -08001066 found_approval = True
James E. Blair64ff4ef2014-01-24 13:50:23 -08001067 by = approval.get('by', {})
James E. Blairc053d022014-01-22 14:57:33 -08001068 for k, v in rapproval.items():
1069 if k == 'username':
James E. Blair64ff4ef2014-01-24 13:50:23 -08001070 if (by.get('username', '') != v):
James E. Blairc053d022014-01-22 14:57:33 -08001071 found_approval = False
1072 elif k == 'email-filter':
James E. Blair64ff4ef2014-01-24 13:50:23 -08001073 if (not v.search(by.get('email', ''))):
James E. Blairc053d022014-01-22 14:57:33 -08001074 found_approval = False
1075 elif k == 'newer-than':
1076 t = now - v
1077 if (approval['grantedOn'] < t):
1078 found_approval = False
1079 elif k == 'older-than':
1080 t = now - v
1081 if (approval['grantedOn'] >= t):
1082 found_approval = False
1083 else:
1084 if (normalizeCategory(approval['description']) != k or
1085 int(approval['value']) != v):
1086 found_approval = False
1087 if found_approval:
1088 matches_approval = True
1089 break
1090 if not matches_approval:
1091 return False
1092
James E. Blair63bb0ef2013-07-29 17:14:51 -07001093 # timespecs are ORed
1094 matches_timespec = False
1095 for timespec in self.timespecs:
1096 if (event.timespec == timespec):
1097 matches_timespec = True
1098 if self.timespecs and not matches_timespec:
1099 return False
1100
James E. Blairee743612012-05-29 14:49:32 -07001101 return True
James E. Blaireff88162013-07-01 12:44:14 -04001102
1103
1104class Layout(object):
1105 def __init__(self):
1106 self.projects = {}
James E. Blair5a9918a2013-08-27 10:06:27 -07001107 self.pipelines = OrderedDict()
James E. Blaireff88162013-07-01 12:44:14 -04001108 self.jobs = {}
James E. Blairc28d1b02013-07-19 11:37:06 -07001109 self.metajobs = []
James E. Blaireff88162013-07-01 12:44:14 -04001110
1111 def getJob(self, name):
1112 if name in self.jobs:
1113 return self.jobs[name]
1114 job = Job(name)
1115 if name.startswith('^'):
1116 # This is a meta-job
1117 regex = re.compile(name)
James E. Blairc28d1b02013-07-19 11:37:06 -07001118 self.metajobs.append((regex, job))
James E. Blaireff88162013-07-01 12:44:14 -04001119 else:
1120 # Apply attributes from matching meta-jobs
James E. Blairc28d1b02013-07-19 11:37:06 -07001121 for regex, metajob in self.metajobs:
James E. Blaireff88162013-07-01 12:44:14 -04001122 if regex.match(name):
1123 job.copy(metajob)
1124 self.jobs[name] = job
1125 return job