blob: b20c08ea2985ddb5dde2496cec28b84585ed03a9 [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
66 self.success_message = None
Joshua Hesketh3979e3e2014-03-04 11:21:10 +110067 self.footer_message = None
James E. Blair2fa50962013-01-30 21:50:41 -080068 self.dequeue_on_new_patchset = True
James E. Blair4aea70c2012-07-26 14:23:24 -070069 self.job_trees = {} # project -> JobTree
70 self.manager = None
James E. Blaire0487072012-08-29 17:38:31 -070071 self.queues = []
James E. Blair64ed6f22013-07-10 14:07:23 -070072 self.precedence = PRECEDENCE_NORMAL
James E. Blair6c358e72013-07-29 17:06:47 -070073 self.trigger = None
Joshua Hesketh1879cf72013-08-19 14:13:15 +100074 self.start_actions = None
75 self.success_actions = None
76 self.failure_actions = None
Clark Boylan7603a372014-01-21 11:43:20 -080077 self.window = None
78 self.window_floor = None
79 self.window_increase_type = None
80 self.window_increase_factor = None
81 self.window_decrease_type = None
82 self.window_decrease_factor = None
James E. Blair4aea70c2012-07-26 14:23:24 -070083
James E. Blaird09c17a2012-08-07 09:23:14 -070084 def __repr__(self):
85 return '<Pipeline %s>' % self.name
86
James E. Blair4aea70c2012-07-26 14:23:24 -070087 def setManager(self, manager):
88 self.manager = manager
89
90 def addProject(self, project):
91 job_tree = JobTree(None) # Null job == job tree root
92 self.job_trees[project] = job_tree
93 return job_tree
94
95 def getProjects(self):
James E. Blairc3d428e2013-12-03 15:06:48 -080096 return sorted(self.job_trees.keys(), lambda a, b: cmp(a.name, b.name))
James E. Blair4aea70c2012-07-26 14:23:24 -070097
James E. Blaire0487072012-08-29 17:38:31 -070098 def addQueue(self, queue):
99 self.queues.append(queue)
100
101 def getQueue(self, project):
102 for queue in self.queues:
103 if project in queue.projects:
104 return queue
105 return None
106
James E. Blair4aea70c2012-07-26 14:23:24 -0700107 def getJobTree(self, project):
108 tree = self.job_trees.get(project)
109 return tree
110
111 def getJobs(self, changeish):
James E. Blaird09c17a2012-08-07 09:23:14 -0700112 tree = self.getJobTree(changeish.project)
James E. Blair4aea70c2012-07-26 14:23:24 -0700113 if not tree:
114 return []
115 return changeish.filterJobs(tree.getJobs())
116
James E. Blairfee8d652013-06-07 08:57:52 -0700117 def _findJobsToRun(self, job_trees, item):
James E. Blair4aea70c2012-07-26 14:23:24 -0700118 torun = []
James E. Blairfee8d652013-06-07 08:57:52 -0700119 if item.item_ahead:
James E. Blair4aea70c2012-07-26 14:23:24 -0700120 # Only run jobs if any 'hold' jobs on the change ahead
121 # have completed successfully.
James E. Blairfee8d652013-06-07 08:57:52 -0700122 if self.isHoldingFollowingChanges(item.item_ahead):
James E. Blair4aea70c2012-07-26 14:23:24 -0700123 return []
124 for tree in job_trees:
125 job = tree.job
126 result = None
127 if job:
James E. Blairfee8d652013-06-07 08:57:52 -0700128 if not job.changeMatches(item.change):
James E. Blair4aea70c2012-07-26 14:23:24 -0700129 continue
James E. Blairfee8d652013-06-07 08:57:52 -0700130 build = item.current_build_set.getBuild(job.name)
James E. Blair4aea70c2012-07-26 14:23:24 -0700131 if build:
132 result = build.result
133 else:
134 # There is no build for the root of this job tree,
135 # so we should run it.
136 torun.append(job)
137 # If there is no job, this is a null job tree, and we should
138 # run all of its jobs.
139 if result == 'SUCCESS' or not job:
James E. Blairfee8d652013-06-07 08:57:52 -0700140 torun.extend(self._findJobsToRun(tree.job_trees, item))
James E. Blair4aea70c2012-07-26 14:23:24 -0700141 return torun
142
James E. Blairfee8d652013-06-07 08:57:52 -0700143 def findJobsToRun(self, item):
144 tree = self.getJobTree(item.change.project)
James E. Blair4aea70c2012-07-26 14:23:24 -0700145 if not tree:
146 return []
James E. Blairfee8d652013-06-07 08:57:52 -0700147 return self._findJobsToRun(tree.job_trees, item)
James E. Blair4aea70c2012-07-26 14:23:24 -0700148
James E. Blairbea9ef12013-07-15 11:52:23 -0700149 def haveAllJobsStarted(self, item):
150 for job in self.getJobs(item.change):
151 build = item.current_build_set.getBuild(job.name)
152 if not build or not build.start_time:
153 return False
154 return True
155
James E. Blairfee8d652013-06-07 08:57:52 -0700156 def areAllJobsComplete(self, item):
157 for job in self.getJobs(item.change):
158 build = item.current_build_set.getBuild(job.name)
James E. Blair4aea70c2012-07-26 14:23:24 -0700159 if not build or not build.result:
160 return False
161 return True
162
James E. Blairfee8d652013-06-07 08:57:52 -0700163 def didAllJobsSucceed(self, item):
164 for job in self.getJobs(item.change):
James E. Blair4ec821f2012-08-23 15:28:28 -0700165 if not job.voting:
166 continue
James E. Blairfee8d652013-06-07 08:57:52 -0700167 build = item.current_build_set.getBuild(job.name)
James E. Blair4aea70c2012-07-26 14:23:24 -0700168 if not build:
169 return False
170 if build.result != 'SUCCESS':
171 return False
172 return True
173
James E. Blairfee8d652013-06-07 08:57:52 -0700174 def didAnyJobFail(self, item):
175 for job in self.getJobs(item.change):
James E. Blair4ec821f2012-08-23 15:28:28 -0700176 if not job.voting:
177 continue
James E. Blairfee8d652013-06-07 08:57:52 -0700178 build = item.current_build_set.getBuild(job.name)
James E. Blair0018a6c2013-02-27 14:11:45 -0800179 if build and build.result and (build.result != 'SUCCESS'):
James E. Blair4aea70c2012-07-26 14:23:24 -0700180 return True
181 return False
182
James E. Blairfee8d652013-06-07 08:57:52 -0700183 def isHoldingFollowingChanges(self, item):
184 for job in self.getJobs(item.change):
James E. Blair4aea70c2012-07-26 14:23:24 -0700185 if not job.hold_following_changes:
186 continue
James E. Blairfee8d652013-06-07 08:57:52 -0700187 build = item.current_build_set.getBuild(job.name)
James E. Blair4aea70c2012-07-26 14:23:24 -0700188 if not build:
189 return True
190 if build.result != 'SUCCESS':
191 return True
James E. Blair972e3c72013-08-29 12:04:55 -0700192
James E. Blairfee8d652013-06-07 08:57:52 -0700193 if not item.item_ahead:
James E. Blair4aea70c2012-07-26 14:23:24 -0700194 return False
James E. Blairfee8d652013-06-07 08:57:52 -0700195 return self.isHoldingFollowingChanges(item.item_ahead)
James E. Blair4aea70c2012-07-26 14:23:24 -0700196
James E. Blairfee8d652013-06-07 08:57:52 -0700197 def setResult(self, item, build):
James E. Blair4a28a882013-08-23 15:17:33 -0700198 if build.retry:
199 item.removeBuild(build)
200 elif build.result != 'SUCCESS':
James E. Blair4aea70c2012-07-26 14:23:24 -0700201 # Get a JobTree from a Job so we can find only its dependent jobs
James E. Blairfee8d652013-06-07 08:57:52 -0700202 root = self.getJobTree(item.change.project)
James E. Blair4aea70c2012-07-26 14:23:24 -0700203 tree = root.getJobTreeForJob(build.job)
204 for job in tree.getJobs():
205 fakebuild = Build(job, None)
206 fakebuild.result = 'SKIPPED'
James E. Blairfee8d652013-06-07 08:57:52 -0700207 item.addBuild(fakebuild)
James E. Blair4aea70c2012-07-26 14:23:24 -0700208
James E. Blair6736beb2013-07-11 15:18:15 -0700209 def setUnableToMerge(self, item, msg):
James E. Blairfee8d652013-06-07 08:57:52 -0700210 item.current_build_set.unable_to_merge = True
James E. Blair6736beb2013-07-11 15:18:15 -0700211 item.current_build_set.unable_to_merge_message = msg
James E. Blairfee8d652013-06-07 08:57:52 -0700212 root = self.getJobTree(item.change.project)
James E. Blair973721f2012-08-15 10:19:43 -0700213 for job in root.getJobs():
214 fakebuild = Build(job, None)
215 fakebuild.result = 'SKIPPED'
James E. Blairfee8d652013-06-07 08:57:52 -0700216 item.addBuild(fakebuild)
James E. Blair973721f2012-08-15 10:19:43 -0700217
James E. Blairfee8d652013-06-07 08:57:52 -0700218 def setDequeuedNeedingChange(self, item):
219 item.dequeued_needing_change = True
220 root = self.getJobTree(item.change.project)
James E. Blaircaec0c52012-08-22 14:52:22 -0700221 for job in root.getJobs():
222 fakebuild = Build(job, None)
223 fakebuild.result = 'SKIPPED'
James E. Blairfee8d652013-06-07 08:57:52 -0700224 item.addBuild(fakebuild)
James E. Blaircaec0c52012-08-22 14:52:22 -0700225
James E. Blaire0487072012-08-29 17:38:31 -0700226 def getChangesInQueue(self):
227 changes = []
228 for shared_queue in self.queues:
James E. Blairfee8d652013-06-07 08:57:52 -0700229 changes.extend([x.change for x in shared_queue.queue])
James E. Blaire0487072012-08-29 17:38:31 -0700230 return changes
231
James E. Blairfee8d652013-06-07 08:57:52 -0700232 def getAllItems(self):
233 items = []
James E. Blaire0487072012-08-29 17:38:31 -0700234 for shared_queue in self.queues:
James E. Blairfee8d652013-06-07 08:57:52 -0700235 items.extend(shared_queue.queue)
James E. Blairfee8d652013-06-07 08:57:52 -0700236 return items
James E. Blaire0487072012-08-29 17:38:31 -0700237
238 def formatStatusHTML(self):
239 ret = ''
240 for queue in self.queues:
241 if len(self.queues) > 1:
242 s = 'Change queue: %s' % queue.name
243 ret += s + '\n'
244 ret += '-' * len(s) + '\n'
James E. Blair972e3c72013-08-29 12:04:55 -0700245 for item in queue.queue:
246 ret += self.formatStatus(item, html=True)
James E. Blaire0487072012-08-29 17:38:31 -0700247 return ret
248
James E. Blair8dbd56a2012-12-22 10:55:10 -0800249 def formatStatusJSON(self):
250 j_pipeline = dict(name=self.name,
251 description=self.description)
252 j_queues = []
253 j_pipeline['change_queues'] = j_queues
254 for queue in self.queues:
255 j_queue = dict(name=queue.name)
256 j_queues.append(j_queue)
257 j_queue['heads'] = []
Clark Boylanaf2476f2014-01-23 14:47:36 -0800258 j_queue['window'] = queue.window
259 j_queue['dependent'] = queue.dependent
James E. Blair972e3c72013-08-29 12:04:55 -0700260
261 j_changes = []
262 for e in queue.queue:
263 if not e.item_ahead:
264 if j_changes:
265 j_queue['heads'].append(j_changes)
266 j_changes = []
267 j_changes.append(self.formatItemJSON(e))
268 if (len(j_changes) > 1 and
269 (j_changes[-2]['remaining_time'] is not None) and
270 (j_changes[-1]['remaining_time'] is not None)):
271 j_changes[-1]['remaining_time'] = max(
272 j_changes[-2]['remaining_time'],
273 j_changes[-1]['remaining_time'])
274 if j_changes:
James E. Blair8dbd56a2012-12-22 10:55:10 -0800275 j_queue['heads'].append(j_changes)
276 return j_pipeline
277
James E. Blairfee8d652013-06-07 08:57:52 -0700278 def formatStatus(self, item, indent=0, html=False):
279 changeish = item.change
James E. Blaire0487072012-08-29 17:38:31 -0700280 indent_str = ' ' * indent
281 ret = ''
282 if html and hasattr(changeish, 'url') and changeish.url is not None:
283 ret += '%sProject %s change <a href="%s">%s</a>\n' % (
284 indent_str,
285 changeish.project.name,
286 changeish.url,
287 changeish._id())
288 else:
James E. Blair972e3c72013-08-29 12:04:55 -0700289 ret += '%sProject %s change %s based on %s\n' % (
290 indent_str,
291 changeish.project.name,
292 changeish._id(),
293 item.item_ahead)
James E. Blaire0487072012-08-29 17:38:31 -0700294 for job in self.getJobs(changeish):
James E. Blairfee8d652013-06-07 08:57:52 -0700295 build = item.current_build_set.getBuild(job.name)
James E. Blaire0487072012-08-29 17:38:31 -0700296 if build:
297 result = build.result
298 else:
299 result = None
300 job_name = job.name
301 if not job.voting:
302 voting = ' (non-voting)'
303 else:
304 voting = ''
305 if html:
306 if build:
307 url = build.url
308 else:
309 url = None
310 if url is not None:
311 job_name = '<a href="%s">%s</a>' % (url, job_name)
312 ret += '%s %s: %s%s' % (indent_str, job_name, result, voting)
313 ret += '\n'
James E. Blaire0487072012-08-29 17:38:31 -0700314 return ret
315
James E. Blairfee8d652013-06-07 08:57:52 -0700316 def formatItemJSON(self, item):
317 changeish = item.change
James E. Blair8dbd56a2012-12-22 10:55:10 -0800318 ret = {}
Clark Boylanaf2476f2014-01-23 14:47:36 -0800319 ret['active'] = item.active
James E. Blair8dbd56a2012-12-22 10:55:10 -0800320 if hasattr(changeish, 'url') and changeish.url is not None:
321 ret['url'] = changeish.url
James E. Blairc44b1382012-12-23 09:39:55 -0800322 else:
323 ret['url'] = None
James E. Blair8dbd56a2012-12-22 10:55:10 -0800324 ret['id'] = changeish._id()
James E. Blair2feda2d2013-09-13 11:48:19 -0700325 if item.item_ahead:
326 ret['item_ahead'] = item.item_ahead.change._id()
327 else:
328 ret['item_ahead'] = None
329 ret['items_behind'] = [i.change._id() for i in item.items_behind]
330 ret['failing_reasons'] = item.current_build_set.failing_reasons
James E. Blair062c4fb2013-09-26 07:46:00 -0700331 ret['zuul_ref'] = item.current_build_set.ref
James E. Blair8dbd56a2012-12-22 10:55:10 -0800332 ret['project'] = changeish.project.name
James E. Blairfee8d652013-06-07 08:57:52 -0700333 ret['enqueue_time'] = int(item.enqueue_time * 1000)
James E. Blair8dbd56a2012-12-22 10:55:10 -0800334 ret['jobs'] = []
James E. Blairbea9ef12013-07-15 11:52:23 -0700335 max_remaining = 0
James E. Blair8dbd56a2012-12-22 10:55:10 -0800336 for job in self.getJobs(changeish):
James E. Blairbea9ef12013-07-15 11:52:23 -0700337 now = time.time()
James E. Blairfee8d652013-06-07 08:57:52 -0700338 build = item.current_build_set.getBuild(job.name)
James E. Blairbea9ef12013-07-15 11:52:23 -0700339 elapsed = None
340 remaining = None
341 result = None
342 url = None
James E. Blair8dbd56a2012-12-22 10:55:10 -0800343 if build:
344 result = build.result
345 url = build.url
James E. Blairbea9ef12013-07-15 11:52:23 -0700346 if build.start_time:
347 if build.end_time:
348 elapsed = int((build.end_time -
349 build.start_time) * 1000)
350 remaining = 0
351 else:
352 elapsed = int((now - build.start_time) * 1000)
353 if build.estimated_time:
354 remaining = max(
355 int(build.estimated_time * 1000) - elapsed,
356 0)
357 if remaining and remaining > max_remaining:
358 max_remaining = remaining
James E. Blair8dbd56a2012-12-22 10:55:10 -0800359 ret['jobs'].append(
360 dict(
361 name=job.name,
James E. Blairbea9ef12013-07-15 11:52:23 -0700362 elapsed_time=elapsed,
363 remaining_time=remaining,
James E. Blair8dbd56a2012-12-22 10:55:10 -0800364 url=url,
365 result=result,
366 voting=job.voting))
James E. Blairbea9ef12013-07-15 11:52:23 -0700367 if self.haveAllJobsStarted(item):
James E. Blair972e3c72013-08-29 12:04:55 -0700368 ret['remaining_time'] = max_remaining
James E. Blairbea9ef12013-07-15 11:52:23 -0700369 else:
370 ret['remaining_time'] = None
James E. Blair8dbd56a2012-12-22 10:55:10 -0800371 return ret
372
James E. Blair4aea70c2012-07-26 14:23:24 -0700373
Joshua Hesketh1879cf72013-08-19 14:13:15 +1000374class ActionReporter(object):
375 """An ActionReporter has a reporter and its configured paramaters"""
376
377 def __repr__(self):
378 return '<ActionReporter %s, %s>' % (self.reporter, self.params)
379
380 def __init__(self, reporter, params):
381 self.reporter = reporter
382 self.params = params
383
384 def report(self, change, message):
385 """Sends the built message off to the configured reporter.
386 Takes the change and message and adds the configured parameters.
387 """
388 return self.reporter.report(change, message, self.params)
389
390 def getSubmitAllowNeeds(self):
391 """Gets the submit allow needs from the reporter based off the
392 parameters."""
393 return self.reporter.getSubmitAllowNeeds(self.params)
394
395
James E. Blairee743612012-05-29 14:49:32 -0700396class ChangeQueue(object):
James E. Blair4aea70c2012-07-26 14:23:24 -0700397 """DependentPipelines have multiple parallel queues shared by
398 different projects; this is one of them. For instance, there may
399 a queue shared by interrelated projects foo and bar, and a second
400 queue for independent project baz. Pipelines have one or more
401 PipelineQueues."""
Clark Boylan7603a372014-01-21 11:43:20 -0800402 def __init__(self, pipeline, dependent=True, window=0, window_floor=1,
403 window_increase_type='linear', window_increase_factor=1,
404 window_decrease_type='exponential', window_decrease_factor=2):
James E. Blair4aea70c2012-07-26 14:23:24 -0700405 self.pipeline = pipeline
James E. Blairee743612012-05-29 14:49:32 -0700406 self.name = ''
James E. Blairee743612012-05-29 14:49:32 -0700407 self.projects = []
408 self._jobs = set()
409 self.queue = []
James E. Blaire0487072012-08-29 17:38:31 -0700410 self.dependent = dependent
Clark Boylan7603a372014-01-21 11:43:20 -0800411 self.window = window
412 self.window_floor = window_floor
413 self.window_increase_type = window_increase_type
414 self.window_increase_factor = window_increase_factor
415 self.window_decrease_type = window_decrease_type
416 self.window_decrease_factor = window_decrease_factor
James E. Blairee743612012-05-29 14:49:32 -0700417
James E. Blair9f9667e2012-06-12 17:51:08 -0700418 def __repr__(self):
James E. Blair4aea70c2012-07-26 14:23:24 -0700419 return '<ChangeQueue %s: %s>' % (self.pipeline.name, self.name)
James E. Blairee743612012-05-29 14:49:32 -0700420
421 def getJobs(self):
422 return self._jobs
423
424 def addProject(self, project):
425 if project not in self.projects:
426 self.projects.append(project)
427 names = [x.name for x in self.projects]
428 names.sort()
429 self.name = ', '.join(names)
James E. Blair4aea70c2012-07-26 14:23:24 -0700430 self._jobs |= set(self.pipeline.getJobTree(project).getJobs())
James E. Blairee743612012-05-29 14:49:32 -0700431
432 def enqueueChange(self, change):
James E. Blair4a035d92014-01-23 13:10:48 -0800433 item = QueueItem(self.pipeline, change)
James E. Blaircdccd972013-07-01 12:10:22 -0700434 self.enqueueItem(item)
435 item.enqueue_time = time.time()
436 return item
437
438 def enqueueItem(self, item):
James E. Blair4a035d92014-01-23 13:10:48 -0800439 item.pipeline = self.pipeline
James E. Blair75241582012-08-31 12:16:55 -0700440 if self.dependent and self.queue:
James E. Blairfee8d652013-06-07 08:57:52 -0700441 item.item_ahead = self.queue[-1]
James E. Blair972e3c72013-08-29 12:04:55 -0700442 item.item_ahead.items_behind.append(item)
James E. Blairfee8d652013-06-07 08:57:52 -0700443 self.queue.append(item)
James E. Blairee743612012-05-29 14:49:32 -0700444
James E. Blairfee8d652013-06-07 08:57:52 -0700445 def dequeueItem(self, item):
446 if item in self.queue:
447 self.queue.remove(item)
James E. Blairfee8d652013-06-07 08:57:52 -0700448 if item.item_ahead:
James E. Blair972e3c72013-08-29 12:04:55 -0700449 item.item_ahead.items_behind.remove(item)
450 for item_behind in item.items_behind:
451 if item.item_ahead:
452 item.item_ahead.items_behind.append(item_behind)
453 item_behind.item_ahead = item.item_ahead
James E. Blairfee8d652013-06-07 08:57:52 -0700454 item.item_ahead = None
James E. Blair972e3c72013-08-29 12:04:55 -0700455 item.items_behind = []
James E. Blairfee8d652013-06-07 08:57:52 -0700456 item.dequeue_time = time.time()
James E. Blaire0487072012-08-29 17:38:31 -0700457
James E. Blair972e3c72013-08-29 12:04:55 -0700458 def moveItem(self, item, item_ahead):
459 if not self.dependent:
460 return False
461 if item.item_ahead == item_ahead:
462 return False
463 # Remove from current location
464 if item.item_ahead:
465 item.item_ahead.items_behind.remove(item)
466 for item_behind in item.items_behind:
467 if item.item_ahead:
468 item.item_ahead.items_behind.append(item_behind)
469 item_behind.item_ahead = item.item_ahead
470 # Add to new location
471 item.item_ahead = item_ahead
James E. Blair00451262013-09-20 11:40:17 -0700472 item.items_behind = []
James E. Blair972e3c72013-08-29 12:04:55 -0700473 if item.item_ahead:
474 item.item_ahead.items_behind.append(item)
475 return True
James E. Blairee743612012-05-29 14:49:32 -0700476
477 def mergeChangeQueue(self, other):
478 for project in other.projects:
479 self.addProject(project)
Clark Boylan7603a372014-01-21 11:43:20 -0800480 self.window = min(self.window, other.window)
481 # TODO merge semantics
482
Clark Boylan3d2f7a72014-01-23 11:07:42 -0800483 def isActionable(self, item):
Clark Boylan7603a372014-01-21 11:43:20 -0800484 if self.dependent and self.window:
Clark Boylan3d2f7a72014-01-23 11:07:42 -0800485 return item in self.queue[:self.window]
Clark Boylan7603a372014-01-21 11:43:20 -0800486 else:
Clark Boylan3d2f7a72014-01-23 11:07:42 -0800487 return True
Clark Boylan7603a372014-01-21 11:43:20 -0800488
489 def increaseWindowSize(self):
490 if self.dependent:
491 if self.window_increase_type == 'linear':
492 self.window += self.window_increase_factor
493 elif self.window_increase_type == 'exponential':
494 self.window *= self.window_increase_factor
495
496 def decreaseWindowSize(self):
497 if self.dependent:
498 if self.window_decrease_type == 'linear':
499 self.window = max(
500 self.window_floor,
501 self.window - self.window_decrease_factor)
502 elif self.window_decrease_type == 'exponential':
503 self.window = max(
504 self.window_floor,
505 self.window / self.window_decrease_factor)
James E. Blairee743612012-05-29 14:49:32 -0700506
James E. Blair1e8dd892012-05-30 09:15:05 -0700507
James E. Blair4aea70c2012-07-26 14:23:24 -0700508class Project(object):
509 def __init__(self, name):
510 self.name = name
James E. Blair19deff22013-08-25 13:17:35 -0700511 self.merge_mode = MERGER_MERGE_RESOLVE
James E. Blair4aea70c2012-07-26 14:23:24 -0700512
513 def __str__(self):
514 return self.name
515
516 def __repr__(self):
517 return '<Project %s>' % (self.name)
518
519
James E. Blairee743612012-05-29 14:49:32 -0700520class Job(object):
521 def __init__(self, name):
James E. Blair222d4982012-07-16 09:31:19 -0700522 # If you add attributes here, be sure to add them to the copy method.
James E. Blairee743612012-05-29 14:49:32 -0700523 self.name = name
524 self.failure_message = None
525 self.success_message = None
James E. Blair6aea36d2012-12-17 13:03:24 -0800526 self.failure_pattern = None
527 self.success_pattern = None
James E. Blaire5a847f2012-07-10 15:29:14 -0700528 self.parameter_function = None
James E. Blair222d4982012-07-16 09:31:19 -0700529 self.hold_following_changes = False
James E. Blair4ec821f2012-08-23 15:28:28 -0700530 self.voting = True
James E. Blaire421a232012-07-25 16:59:21 -0700531 self.branches = []
532 self._branches = []
James E. Blair70c71582013-03-06 08:50:50 -0800533 self.files = []
534 self._files = []
James E. Blairee743612012-05-29 14:49:32 -0700535
536 def __str__(self):
537 return self.name
538
539 def __repr__(self):
540 return '<Job %s>' % (self.name)
541
James E. Blairb0954652012-06-01 11:32:01 -0700542 def copy(self, other):
James E. Blairc28d1b02013-07-19 11:37:06 -0700543 if other.failure_message:
544 self.failure_message = other.failure_message
545 if other.success_message:
546 self.success_message = other.success_message
547 if other.failure_pattern:
548 self.failure_pattern = other.failure_pattern
549 if other.success_pattern:
550 self.success_pattern = other.success_pattern
551 if other.parameter_function:
552 self.parameter_function = other.parameter_function
553 if other.branches:
554 self.branches = other.branches[:]
555 self._branches = other._branches[:]
556 if other.files:
557 self.files = other.files[:]
558 self._files = other._files[:]
James E. Blair222d4982012-07-16 09:31:19 -0700559 self.hold_following_changes = other.hold_following_changes
James E. Blair4ec821f2012-08-23 15:28:28 -0700560 self.voting = other.voting
James E. Blairb0954652012-06-01 11:32:01 -0700561
James E. Blaire421a232012-07-25 16:59:21 -0700562 def changeMatches(self, change):
James E. Blair70c71582013-03-06 08:50:50 -0800563 matches_branch = False
James E. Blaire421a232012-07-25 16:59:21 -0700564 for branch in self.branches:
James E. Blair45865f32012-10-05 09:39:46 -0700565 if hasattr(change, 'branch') and branch.match(change.branch):
James E. Blair70c71582013-03-06 08:50:50 -0800566 matches_branch = True
James E. Blair45865f32012-10-05 09:39:46 -0700567 if hasattr(change, 'ref') and branch.match(change.ref):
James E. Blair70c71582013-03-06 08:50:50 -0800568 matches_branch = True
569 if self.branches and not matches_branch:
570 return False
571
572 matches_file = False
573 for f in self.files:
574 if hasattr(change, 'files'):
575 for cf in change.files:
576 if f.match(cf):
577 matches_file = True
578 if self.files and not matches_file:
579 return False
580
581 return True
James E. Blaire5a847f2012-07-10 15:29:14 -0700582
James E. Blair1e8dd892012-05-30 09:15:05 -0700583
James E. Blairee743612012-05-29 14:49:32 -0700584class JobTree(object):
585 """ A JobTree represents an instance of one Job, and holds JobTrees
586 whose jobs should be run if that Job succeeds. A root node of a
587 JobTree will have no associated Job. """
588
589 def __init__(self, job):
590 self.job = job
591 self.job_trees = []
592
593 def addJob(self, job):
James E. Blair3aa37272013-09-27 08:33:10 -0700594 t = JobTree(job)
595 self.job_trees.append(t)
596 return t
James E. Blairee743612012-05-29 14:49:32 -0700597
598 def getJobs(self):
599 jobs = []
600 for x in self.job_trees:
601 jobs.append(x.job)
602 jobs.extend(x.getJobs())
603 return jobs
604
605 def getJobTreeForJob(self, job):
606 if self.job == job:
607 return self
608 for tree in self.job_trees:
609 ret = tree.getJobTreeForJob(job)
610 if ret:
611 return ret
612 return None
613
James E. Blair1e8dd892012-05-30 09:15:05 -0700614
James E. Blair4aea70c2012-07-26 14:23:24 -0700615class Build(object):
616 def __init__(self, job, uuid):
617 self.job = job
618 self.uuid = uuid
James E. Blair4aea70c2012-07-26 14:23:24 -0700619 self.url = None
620 self.number = None
621 self.result = None
622 self.build_set = None
623 self.launch_time = time.time()
James E. Blair71e94122012-12-24 17:53:08 -0800624 self.start_time = None
625 self.end_time = None
James E. Blairbea9ef12013-07-15 11:52:23 -0700626 self.estimated_time = None
James E. Blair66eeebf2013-07-27 17:44:32 -0700627 self.pipeline = None
James E. Blair0aac4872013-08-23 14:02:38 -0700628 self.canceled = False
James E. Blair4a28a882013-08-23 15:17:33 -0700629 self.retry = False
James E. Blaird78576a2013-07-09 10:39:17 -0700630 self.parameters = {}
Joshua Heskethba8776a2014-01-12 14:35:40 +0800631 self.worker = Worker()
James E. Blairee743612012-05-29 14:49:32 -0700632
633 def __repr__(self):
Joshua Heskethba8776a2014-01-12 14:35:40 +0800634 return ('<Build %s of %s on %s>' %
635 (self.uuid, self.job.name, self.worker))
636
637
638class Worker(object):
639 """A model of the worker running a job"""
640 def __init__(self):
641 self.name = "Unknown"
642 self.hostname = None
643 self.ips = []
644 self.fqdn = None
645 self.program = None
646 self.version = None
647 self.extra = {}
648
649 def updateFromData(self, data):
650 """Update worker information if contained in the WORK_DATA response."""
651 self.name = data.get('worker_name', self.name)
652 self.hostname = data.get('worker_hostname', self.hostname)
653 self.ips = data.get('worker_ips', self.ips)
654 self.fqdn = data.get('worker_fqdn', self.fqdn)
655 self.program = data.get('worker_program', self.program)
656 self.version = data.get('worker_version', self.version)
657 self.extra = data.get('worker_extra', self.extra)
658
659 def __repr__(self):
660 return '<Worker %s>' % self.name
James E. Blairee743612012-05-29 14:49:32 -0700661
James E. Blair1e8dd892012-05-30 09:15:05 -0700662
James E. Blair7e530ad2012-07-03 16:12:28 -0700663class BuildSet(object):
James E. Blair4076e2b2014-01-28 12:42:20 -0800664 # Merge states:
665 NEW = 1
666 PENDING = 2
667 COMPLETE = 3
668
James E. Blairfee8d652013-06-07 08:57:52 -0700669 def __init__(self, item):
670 self.item = item
James E. Blair11700c32012-07-05 17:50:05 -0700671 self.other_changes = []
James E. Blair7e530ad2012-07-03 16:12:28 -0700672 self.builds = {}
James E. Blair11700c32012-07-05 17:50:05 -0700673 self.result = None
674 self.next_build_set = None
675 self.previous_build_set = None
James E. Blair4886cc12012-07-18 15:39:41 -0700676 self.ref = None
James E. Blair81515ad2012-10-01 18:29:08 -0700677 self.commit = None
James E. Blair4076e2b2014-01-28 12:42:20 -0800678 self.zuul_url = None
James E. Blair973721f2012-08-15 10:19:43 -0700679 self.unable_to_merge = False
James E. Blair6736beb2013-07-11 15:18:15 -0700680 self.unable_to_merge_message = None
James E. Blair972e3c72013-08-29 12:04:55 -0700681 self.failing_reasons = []
James E. Blair4076e2b2014-01-28 12:42:20 -0800682 self.merge_state = self.NEW
James E. Blair7e530ad2012-07-03 16:12:28 -0700683
James E. Blair4886cc12012-07-18 15:39:41 -0700684 def setConfiguration(self):
James E. Blair11700c32012-07-05 17:50:05 -0700685 # The change isn't enqueued until after it's created
686 # so we don't know what the other changes ahead will be
687 # until jobs start.
688 if not self.other_changes:
James E. Blairfee8d652013-06-07 08:57:52 -0700689 next_item = self.item.item_ahead
690 while next_item:
691 self.other_changes.append(next_item.change)
692 next_item = next_item.item_ahead
James E. Blair4886cc12012-07-18 15:39:41 -0700693 if not self.ref:
694 self.ref = 'Z' + uuid4().hex
695
James E. Blair4886cc12012-07-18 15:39:41 -0700696 def addBuild(self, build):
697 self.builds[build.job.name] = build
698 build.build_set = self
James E. Blair11700c32012-07-05 17:50:05 -0700699
James E. Blair4a28a882013-08-23 15:17:33 -0700700 def removeBuild(self, build):
701 del self.builds[build.job.name]
702
James E. Blair7e530ad2012-07-03 16:12:28 -0700703 def getBuild(self, job_name):
704 return self.builds.get(job_name)
705
James E. Blair11700c32012-07-05 17:50:05 -0700706 def getBuilds(self):
707 keys = self.builds.keys()
708 keys.sort()
709 return [self.builds.get(x) for x in keys]
710
James E. Blair7e530ad2012-07-03 16:12:28 -0700711
James E. Blairfee8d652013-06-07 08:57:52 -0700712class QueueItem(object):
713 """A changish inside of a Pipeline queue"""
James E. Blair32663402012-06-01 10:04:18 -0700714
James E. Blair4a035d92014-01-23 13:10:48 -0800715 def __init__(self, pipeline, change):
James E. Blairfee8d652013-06-07 08:57:52 -0700716 self.pipeline = pipeline
717 self.change = change # a changeish
James E. Blair7e530ad2012-07-03 16:12:28 -0700718 self.build_sets = []
James E. Blaircaec0c52012-08-22 14:52:22 -0700719 self.dequeued_needing_change = False
James E. Blair11700c32012-07-05 17:50:05 -0700720 self.current_build_set = BuildSet(self)
721 self.build_sets.append(self.current_build_set)
James E. Blairfee8d652013-06-07 08:57:52 -0700722 self.item_ahead = None
James E. Blair972e3c72013-08-29 12:04:55 -0700723 self.items_behind = []
James E. Blair8fa16972013-01-15 16:57:20 -0800724 self.enqueue_time = None
725 self.dequeue_time = None
James E. Blairfee8d652013-06-07 08:57:52 -0700726 self.reported = False
Clark Boylanaf2476f2014-01-23 14:47:36 -0800727 self.active = False
James E. Blaire5a847f2012-07-10 15:29:14 -0700728
James E. Blair972e3c72013-08-29 12:04:55 -0700729 def __repr__(self):
730 if self.pipeline:
731 pipeline = self.pipeline.name
732 else:
733 pipeline = None
734 return '<QueueItem 0x%x for %s in %s>' % (
735 id(self), self.change, pipeline)
736
James E. Blairee743612012-05-29 14:49:32 -0700737 def resetAllBuilds(self):
James E. Blair11700c32012-07-05 17:50:05 -0700738 old = self.current_build_set
739 self.current_build_set.result = 'CANCELED'
740 self.current_build_set = BuildSet(self)
741 old.next_build_set = self.current_build_set
742 self.current_build_set.previous_build_set = old
James E. Blair7e530ad2012-07-03 16:12:28 -0700743 self.build_sets.append(self.current_build_set)
James E. Blairee743612012-05-29 14:49:32 -0700744
745 def addBuild(self, build):
James E. Blair7e530ad2012-07-03 16:12:28 -0700746 self.current_build_set.addBuild(build)
James E. Blair66eeebf2013-07-27 17:44:32 -0700747 build.pipeline = self.pipeline
James E. Blairee743612012-05-29 14:49:32 -0700748
James E. Blair4a28a882013-08-23 15:17:33 -0700749 def removeBuild(self, build):
750 self.current_build_set.removeBuild(build)
751
James E. Blairfee8d652013-06-07 08:57:52 -0700752 def setReportedResult(self, result):
753 self.current_build_set.result = result
754
755
756class Changeish(object):
757 """Something like a change; either a change or a ref"""
James E. Blairfee8d652013-06-07 08:57:52 -0700758
759 def __init__(self, project):
760 self.project = project
761
762 def equals(self, other):
763 raise NotImplementedError()
764
765 def isUpdateOf(self, other):
766 raise NotImplementedError()
767
768 def filterJobs(self, jobs):
769 return filter(lambda job: job.changeMatches(self), jobs)
770
771 def getRelatedChanges(self):
772 return set()
773
James E. Blair1e8dd892012-05-30 09:15:05 -0700774
James E. Blair4aea70c2012-07-26 14:23:24 -0700775class Change(Changeish):
James E. Blair4aea70c2012-07-26 14:23:24 -0700776 def __init__(self, project):
777 super(Change, self).__init__(project)
778 self.branch = None
779 self.number = None
780 self.url = None
781 self.patchset = None
782 self.refspec = None
783
James E. Blair70c71582013-03-06 08:50:50 -0800784 self.files = []
James E. Blair4aea70c2012-07-26 14:23:24 -0700785 self.needs_change = None
786 self.needed_by_changes = []
787 self.is_current_patchset = True
788 self.can_merge = False
789 self.is_merged = False
James E. Blairfee8d652013-06-07 08:57:52 -0700790 self.failed_to_merge = False
James E. Blairc053d022014-01-22 14:57:33 -0800791 self.approvals = []
James E. Blair4aea70c2012-07-26 14:23:24 -0700792
793 def _id(self):
James E. Blairbe765db2012-08-07 08:36:20 -0700794 return '%s,%s' % (self.number, self.patchset)
James E. Blair4aea70c2012-07-26 14:23:24 -0700795
796 def __repr__(self):
797 return '<Change 0x%x %s>' % (id(self), self._id())
798
799 def equals(self, other):
Zhongyue Luoaa85ebf2012-09-21 16:38:33 +0800800 if self.number == other.number and self.patchset == other.patchset:
James E. Blair4aea70c2012-07-26 14:23:24 -0700801 return True
802 return False
803
James E. Blair2fa50962013-01-30 21:50:41 -0800804 def isUpdateOf(self, other):
Clark Boylan01976242013-02-17 18:41:48 -0800805 if ((hasattr(other, 'number') and self.number == other.number) and
James E. Blair7a192e42013-07-11 14:10:36 -0700806 (hasattr(other, 'patchset') and
807 self.patchset is not None and
808 other.patchset is not None and
809 int(self.patchset) > int(other.patchset))):
James E. Blair2fa50962013-01-30 21:50:41 -0800810 return True
811 return False
812
James E. Blairfee8d652013-06-07 08:57:52 -0700813 def getRelatedChanges(self):
814 related = set()
815 if self.needs_change:
816 related.add(self.needs_change)
817 for c in self.needed_by_changes:
818 related.add(c)
819 related.update(c.getRelatedChanges())
820 return related
James E. Blair4aea70c2012-07-26 14:23:24 -0700821
822
823class Ref(Changeish):
James E. Blair4aea70c2012-07-26 14:23:24 -0700824 def __init__(self, project):
James E. Blairbe765db2012-08-07 08:36:20 -0700825 super(Ref, self).__init__(project)
James E. Blair4aea70c2012-07-26 14:23:24 -0700826 self.ref = None
827 self.oldrev = None
828 self.newrev = None
829
James E. Blairbe765db2012-08-07 08:36:20 -0700830 def _id(self):
831 return self.newrev
832
Antoine Musso68bdcd72013-01-17 12:31:28 +0100833 def __repr__(self):
834 rep = None
835 if self.newrev == '0000000000000000000000000000000000000000':
836 rep = '<Ref 0x%x deletes %s from %s' % (
837 id(self), self.ref, self.oldrev)
838 elif self.oldrev == '0000000000000000000000000000000000000000':
839 rep = '<Ref 0x%x creates %s on %s>' % (
840 id(self), self.ref, self.newrev)
841 else:
842 # Catch all
843 rep = '<Ref 0x%x %s updated %s..%s>' % (
844 id(self), self.ref, self.oldrev, self.newrev)
845
846 return rep
847
James E. Blair4aea70c2012-07-26 14:23:24 -0700848 def equals(self, other):
James E. Blair9358c612012-09-28 08:29:39 -0700849 if (self.project == other.project
850 and self.ref == other.ref
851 and self.newrev == other.newrev):
James E. Blair4aea70c2012-07-26 14:23:24 -0700852 return True
853 return False
854
James E. Blair2fa50962013-01-30 21:50:41 -0800855 def isUpdateOf(self, other):
856 return False
857
James E. Blair4aea70c2012-07-26 14:23:24 -0700858
James E. Blair63bb0ef2013-07-29 17:14:51 -0700859class NullChange(Changeish):
James E. Blaire5910202013-12-27 09:50:31 -0800860 def __repr__(self):
861 return '<NullChange for %s>' % (self.project)
James E. Blair63bb0ef2013-07-29 17:14:51 -0700862
James E. Blair63bb0ef2013-07-29 17:14:51 -0700863 def _id(self):
Alex Gaynorddb9ef32013-09-16 21:04:58 -0700864 return None
James E. Blair63bb0ef2013-07-29 17:14:51 -0700865
866 def equals(self, other):
867 return False
868
869 def isUpdateOf(self, other):
870 return False
871
872
James E. Blairee743612012-05-29 14:49:32 -0700873class TriggerEvent(object):
874 def __init__(self):
875 self.data = None
James E. Blair32663402012-06-01 10:04:18 -0700876 # common
James E. Blairee743612012-05-29 14:49:32 -0700877 self.type = None
878 self.project_name = None
James E. Blair6c358e72013-07-29 17:06:47 -0700879 self.trigger_name = None
Antoine Mussob4e809e2012-12-06 16:58:06 +0100880 # Representation of the user account that performed the event.
881 self.account = None
James E. Blair32663402012-06-01 10:04:18 -0700882 # patchset-created, comment-added, etc.
James E. Blairee743612012-05-29 14:49:32 -0700883 self.change_number = None
Clark Boylanfc56df32012-06-28 15:25:57 -0700884 self.change_url = None
James E. Blairee743612012-05-29 14:49:32 -0700885 self.patch_number = None
James E. Blaira03262c2012-05-30 09:41:16 -0700886 self.refspec = None
James E. Blairee743612012-05-29 14:49:32 -0700887 self.approvals = []
888 self.branch = None
Clark Boylanb9bcb402012-06-29 17:44:05 -0700889 self.comment = None
James E. Blair32663402012-06-01 10:04:18 -0700890 # ref-updated
James E. Blairee743612012-05-29 14:49:32 -0700891 self.ref = None
James E. Blair32663402012-06-01 10:04:18 -0700892 self.oldrev = None
James E. Blair89cae0f2012-07-18 11:18:32 -0700893 self.newrev = None
James E. Blair63bb0ef2013-07-29 17:14:51 -0700894 # timer
895 self.timespec = None
James E. Blairad28e912013-11-27 10:43:22 -0800896 # For events that arrive with a destination pipeline (eg, from
897 # an admin command, etc):
898 self.forced_pipeline = None
James E. Blairee743612012-05-29 14:49:32 -0700899
James E. Blair9f9667e2012-06-12 17:51:08 -0700900 def __repr__(self):
James E. Blairee743612012-05-29 14:49:32 -0700901 ret = '<TriggerEvent %s %s' % (self.type, self.project_name)
James E. Blair1e8dd892012-05-30 09:15:05 -0700902
James E. Blairee743612012-05-29 14:49:32 -0700903 if self.branch:
904 ret += " %s" % self.branch
905 if self.change_number:
906 ret += " %s,%s" % (self.change_number, self.patch_number)
907 if self.approvals:
James E. Blair1e8dd892012-05-30 09:15:05 -0700908 ret += ' ' + ', '.join(
909 ['%s:%s' % (a['type'], a['value']) for a in self.approvals])
James E. Blairee743612012-05-29 14:49:32 -0700910 ret += '>'
911
912 return ret
913
James E. Blair4aea70c2012-07-26 14:23:24 -0700914 def getChange(self, project, trigger):
James E. Blaire421a232012-07-25 16:59:21 -0700915 if self.change_number:
James E. Blair4aea70c2012-07-26 14:23:24 -0700916 change = trigger.getChange(self.change_number, self.patch_number)
James E. Blair63bb0ef2013-07-29 17:14:51 -0700917 elif self.ref:
James E. Blair4aea70c2012-07-26 14:23:24 -0700918 change = Ref(project)
James E. Blaire421a232012-07-25 16:59:21 -0700919 change.ref = self.ref
920 change.oldrev = self.oldrev
921 change.newrev = self.newrev
James E. Blairc44b1382012-12-23 09:39:55 -0800922 change.url = trigger.getGitwebUrl(project, sha=self.newrev)
James E. Blair63bb0ef2013-07-29 17:14:51 -0700923 else:
924 change = NullChange(project)
James E. Blaire421a232012-07-25 16:59:21 -0700925
926 return change
927
James E. Blair1e8dd892012-05-30 09:15:05 -0700928
James E. Blairee743612012-05-29 14:49:32 -0700929class EventFilter(object):
James E. Blairc053d022014-01-22 14:57:33 -0800930 def __init__(self, types=[], branches=[], refs=[], event_approvals={},
Joshua Heskethb8a817e2013-12-27 11:21:38 +1100931 comment_filters=[], email_filters=[], username_filters=[],
James E. Blairc053d022014-01-22 14:57:33 -0800932 timespecs=[], require_approvals=[]):
James E. Blairee743612012-05-29 14:49:32 -0700933 self._types = types
934 self._branches = branches
935 self._refs = refs
James E. Blaircf429f32012-12-20 14:28:24 -0800936 self._comment_filters = comment_filters
937 self._email_filters = email_filters
Joshua Heskethb8a817e2013-12-27 11:21:38 +1100938 self._username_filters = username_filters
James E. Blairee743612012-05-29 14:49:32 -0700939 self.types = [re.compile(x) for x in types]
940 self.branches = [re.compile(x) for x in branches]
941 self.refs = [re.compile(x) for x in refs]
Clark Boylanb9bcb402012-06-29 17:44:05 -0700942 self.comment_filters = [re.compile(x) for x in comment_filters]
Antoine Mussob4e809e2012-12-06 16:58:06 +0100943 self.email_filters = [re.compile(x) for x in email_filters]
Joshua Heskethb8a817e2013-12-27 11:21:38 +1100944 self.username_filters = [re.compile(x) for x in username_filters]
James E. Blairc053d022014-01-22 14:57:33 -0800945 self.event_approvals = event_approvals
946 self.require_approvals = require_approvals
James E. Blair63bb0ef2013-07-29 17:14:51 -0700947 self.timespecs = timespecs
James E. Blairee743612012-05-29 14:49:32 -0700948
James E. Blairc053d022014-01-22 14:57:33 -0800949 for a in self.require_approvals:
950 if 'older-than' in a:
951 a['older-than'] = time_to_seconds(a['older-than'])
952 if 'newer-than' in a:
953 a['newer-than'] = time_to_seconds(a['newer-than'])
954 if 'email-filter' in a:
955 a['email-filter'] = re.compile(a['email-filter'])
956
James E. Blair9f9667e2012-06-12 17:51:08 -0700957 def __repr__(self):
James E. Blairee743612012-05-29 14:49:32 -0700958 ret = '<EventFilter'
James E. Blair1e8dd892012-05-30 09:15:05 -0700959
James E. Blairee743612012-05-29 14:49:32 -0700960 if self._types:
961 ret += ' types: %s' % ', '.join(self._types)
962 if self._branches:
963 ret += ' branches: %s' % ', '.join(self._branches)
964 if self._refs:
965 ret += ' refs: %s' % ', '.join(self._refs)
James E. Blairc053d022014-01-22 14:57:33 -0800966 if self.event_approvals:
967 ret += ' event_approvals: %s' % ', '.join(
968 ['%s:%s' % a for a in self.event_approvals.items()])
James E. Blaircf429f32012-12-20 14:28:24 -0800969 if self._comment_filters:
970 ret += ' comment_filters: %s' % ', '.join(self._comment_filters)
971 if self._email_filters:
972 ret += ' email_filters: %s' % ', '.join(self._email_filters)
Joshua Heskethb8a817e2013-12-27 11:21:38 +1100973 if self._username_filters:
974 ret += ' username_filters: %s' % ', '.join(self._username_filters)
James E. Blair63bb0ef2013-07-29 17:14:51 -0700975 if self.timespecs:
976 ret += ' timespecs: %s' % ', '.join(self.timespecs)
James E. Blairee743612012-05-29 14:49:32 -0700977 ret += '>'
978
979 return ret
980
James E. Blairc053d022014-01-22 14:57:33 -0800981 def matches(self, event, change):
James E. Blairee743612012-05-29 14:49:32 -0700982 def normalizeCategory(name):
983 name = name.lower()
984 return re.sub(' ', '-', name)
985
986 # event types are ORed
987 matches_type = False
988 for etype in self.types:
989 if etype.match(event.type):
990 matches_type = True
991 if self.types and not matches_type:
992 return False
993
994 # branches are ORed
995 matches_branch = False
996 for branch in self.branches:
997 if branch.match(event.branch):
998 matches_branch = True
999 if self.branches and not matches_branch:
1000 return False
1001
1002 # refs are ORed
1003 matches_ref = False
1004 for ref in self.refs:
1005 if ref.match(event.ref):
1006 matches_ref = True
1007 if self.refs and not matches_ref:
1008 return False
1009
Clark Boylanb9bcb402012-06-29 17:44:05 -07001010 # comment_filters are ORed
1011 matches_comment_filter = False
1012 for comment_filter in self.comment_filters:
1013 if (event.comment is not None and
James E. Blaircf429f32012-12-20 14:28:24 -08001014 comment_filter.search(event.comment)):
Clark Boylanb9bcb402012-06-29 17:44:05 -07001015 matches_comment_filter = True
1016 if self.comment_filters and not matches_comment_filter:
1017 return False
1018
Antoine Mussob4e809e2012-12-06 16:58:06 +01001019 # We better have an account provided by Gerrit to do
1020 # email filtering.
1021 if event.account is not None:
James E. Blaircf429f32012-12-20 14:28:24 -08001022 account_email = event.account.get('email')
Antoine Mussob4e809e2012-12-06 16:58:06 +01001023 # email_filters are ORed
1024 matches_email_filter = False
1025 for email_filter in self.email_filters:
Antoine Mussob4e809e2012-12-06 16:58:06 +01001026 if (account_email is not None and
James E. Blaircf429f32012-12-20 14:28:24 -08001027 email_filter.search(account_email)):
Antoine Mussob4e809e2012-12-06 16:58:06 +01001028 matches_email_filter = True
1029 if self.email_filters and not matches_email_filter:
1030 return False
1031
Joshua Heskethb8a817e2013-12-27 11:21:38 +11001032 # username_filters are ORed
1033 account_username = event.account.get('username')
1034 matches_username_filter = False
1035 for username_filter in self.username_filters:
1036 if (account_username is not None and
1037 username_filter.search(account_username)):
1038 matches_username_filter = True
1039 if self.username_filters and not matches_username_filter:
1040 return False
1041
James E. Blairee743612012-05-29 14:49:32 -07001042 # approvals are ANDed
James E. Blairc053d022014-01-22 14:57:33 -08001043 for category, value in self.event_approvals.items():
James E. Blairee743612012-05-29 14:49:32 -07001044 matches_approval = False
1045 for eapproval in event.approvals:
1046 if (normalizeCategory(eapproval['description']) == category and
1047 int(eapproval['value']) == int(value)):
1048 matches_approval = True
James E. Blair1e8dd892012-05-30 09:15:05 -07001049 if not matches_approval:
1050 return False
James E. Blair63bb0ef2013-07-29 17:14:51 -07001051
James E. Blairc053d022014-01-22 14:57:33 -08001052 if self.require_approvals and not change.approvals:
1053 # A change with no approvals can not match
1054 return False
1055
1056 now = time.time()
1057 for rapproval in self.require_approvals:
1058 matches_approval = False
1059 for approval in change.approvals:
James E. Blairf74f1602014-02-26 09:59:43 -08001060 if 'description' not in approval:
1061 continue
James E. Blairc053d022014-01-22 14:57:33 -08001062 found_approval = True
James E. Blair64ff4ef2014-01-24 13:50:23 -08001063 by = approval.get('by', {})
James E. Blairc053d022014-01-22 14:57:33 -08001064 for k, v in rapproval.items():
1065 if k == 'username':
James E. Blair64ff4ef2014-01-24 13:50:23 -08001066 if (by.get('username', '') != v):
James E. Blairc053d022014-01-22 14:57:33 -08001067 found_approval = False
1068 elif k == 'email-filter':
James E. Blair64ff4ef2014-01-24 13:50:23 -08001069 if (not v.search(by.get('email', ''))):
James E. Blairc053d022014-01-22 14:57:33 -08001070 found_approval = False
1071 elif k == 'newer-than':
1072 t = now - v
1073 if (approval['grantedOn'] < t):
1074 found_approval = False
1075 elif k == 'older-than':
1076 t = now - v
1077 if (approval['grantedOn'] >= t):
1078 found_approval = False
1079 else:
1080 if (normalizeCategory(approval['description']) != k or
1081 int(approval['value']) != v):
1082 found_approval = False
1083 if found_approval:
1084 matches_approval = True
1085 break
1086 if not matches_approval:
1087 return False
1088
James E. Blair63bb0ef2013-07-29 17:14:51 -07001089 # timespecs are ORed
1090 matches_timespec = False
1091 for timespec in self.timespecs:
1092 if (event.timespec == timespec):
1093 matches_timespec = True
1094 if self.timespecs and not matches_timespec:
1095 return False
1096
James E. Blairee743612012-05-29 14:49:32 -07001097 return True
James E. Blaireff88162013-07-01 12:44:14 -04001098
1099
1100class Layout(object):
1101 def __init__(self):
1102 self.projects = {}
James E. Blair5a9918a2013-08-27 10:06:27 -07001103 self.pipelines = OrderedDict()
James E. Blaireff88162013-07-01 12:44:14 -04001104 self.jobs = {}
James E. Blairc28d1b02013-07-19 11:37:06 -07001105 self.metajobs = []
James E. Blaireff88162013-07-01 12:44:14 -04001106
1107 def getJob(self, name):
1108 if name in self.jobs:
1109 return self.jobs[name]
1110 job = Job(name)
1111 if name.startswith('^'):
1112 # This is a meta-job
1113 regex = re.compile(name)
James E. Blairc28d1b02013-07-19 11:37:06 -07001114 self.metajobs.append((regex, job))
James E. Blaireff88162013-07-01 12:44:14 -04001115 else:
1116 # Apply attributes from matching meta-jobs
James E. Blairc28d1b02013-07-19 11:37:06 -07001117 for regex, metajob in self.metajobs:
James E. Blaireff88162013-07-01 12:44:14 -04001118 if regex.match(name):
1119 job.copy(metajob)
1120 self.jobs[name] = job
1121 return job