blob: c8cfc3bd2ab4586bd66d392721935cbaeea04f91 [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. Blairee743612012-05-29 14:49:32 -070017
James E. Blair1e8dd892012-05-30 09:15:05 -070018
James E. Blairee743612012-05-29 14:49:32 -070019class ChangeQueue(object):
20 def __init__(self, queue_name):
21 self.name = ''
22 self.queue_name = queue_name
23 self.projects = []
24 self._jobs = set()
25 self.queue = []
26
27 def __str__(self):
28 return '<ChangeQueue %s: %s>' % (self.queue_name, self.name)
29
30 def getJobs(self):
31 return self._jobs
32
33 def addProject(self, project):
34 if project not in self.projects:
35 self.projects.append(project)
36 names = [x.name for x in self.projects]
37 names.sort()
38 self.name = ', '.join(names)
39 self._jobs |= set(project.getJobs(self.queue_name))
40
41 def enqueueChange(self, change):
42 if self.queue:
43 self.queue[-1].change_behind = change
44 change.change_ahead = self.queue[-1]
45 self.queue.append(change)
46 change.queue = self
47
48 def dequeueChange(self, change):
49 if change in self.queue:
50 self.queue.remove(change)
51
52 def mergeChangeQueue(self, other):
53 for project in other.projects:
54 self.addProject(project)
55
James E. Blair1e8dd892012-05-30 09:15:05 -070056
James E. Blairee743612012-05-29 14:49:32 -070057class Job(object):
58 def __init__(self, name):
59 self.name = name
60 self.failure_message = None
61 self.success_message = None
62 self.event_filters = []
63
64 def __str__(self):
65 return self.name
66
67 def __repr__(self):
68 return '<Job %s>' % (self.name)
69
James E. Blairb0954652012-06-01 11:32:01 -070070 def copy(self, other):
71 self.failure_message = other.failure_message
72 self.success_message = other.failure_message
73 self.event_filters = other.event_filters[:]
74
James E. Blair1e8dd892012-05-30 09:15:05 -070075
James E. Blairee743612012-05-29 14:49:32 -070076class Build(object):
77 def __init__(self, job, uuid):
78 self.job = job
79 self.uuid = uuid
80 self.status = None
81 self.url = None
82 self.number = None
James E. Blairff986a12012-05-30 14:56:51 -070083 self.result = None
84 self.launch_time = time.time()
James E. Blairee743612012-05-29 14:49:32 -070085
86 def __repr__(self):
87 return '<Build %s of %s>' % (self.uuid, self.job.name)
88
James E. Blair1e8dd892012-05-30 09:15:05 -070089
James E. Blairee743612012-05-29 14:49:32 -070090class JobTree(object):
91 """ A JobTree represents an instance of one Job, and holds JobTrees
92 whose jobs should be run if that Job succeeds. A root node of a
93 JobTree will have no associated Job. """
94
95 def __init__(self, job):
96 self.job = job
97 self.job_trees = []
98
99 def addJob(self, job):
100 if job not in [x.job for x in self.job_trees]:
101 t = JobTree(job)
102 self.job_trees.append(t)
103 return t
104
105 def getJobs(self):
106 jobs = []
107 for x in self.job_trees:
108 jobs.append(x.job)
109 jobs.extend(x.getJobs())
110 return jobs
111
112 def getJobTreeForJob(self, job):
113 if self.job == job:
114 return self
115 for tree in self.job_trees:
116 ret = tree.getJobTreeForJob(job)
117 if ret:
118 return ret
119 return None
120
James E. Blair1e8dd892012-05-30 09:15:05 -0700121
James E. Blairee743612012-05-29 14:49:32 -0700122class Project(object):
123 def __init__(self, name):
124 self.name = name
125 self.job_trees = {} # Queue -> JobTree
126
127 def __str__(self):
128 return self.name
129
130 def __repr__(self):
131 return '<Project %s>' % (self.name)
132
133 def addQueue(self, name):
134 self.job_trees[name] = JobTree(None)
135 return self.job_trees[name]
136
137 def hasQueue(self, name):
James E. Blair1e8dd892012-05-30 09:15:05 -0700138 if name in self.job_trees:
James E. Blairee743612012-05-29 14:49:32 -0700139 return True
140 return False
141
142 def getJobTreeForQueue(self, name):
143 return self.job_trees.get(name, None)
144
145 def getJobs(self, queue_name):
146 tree = self.getJobTreeForQueue(queue_name)
147 if not tree:
148 return []
149 return tree.getJobs()
150
James E. Blair1e8dd892012-05-30 09:15:05 -0700151
James E. Blairee743612012-05-29 14:49:32 -0700152class Change(object):
James E. Blair32663402012-06-01 10:04:18 -0700153 def __init__(self, queue_name, project, event):
James E. Blairee743612012-05-29 14:49:32 -0700154 self.queue_name = queue_name
155 self.project = project
James E. Blair32663402012-06-01 10:04:18 -0700156 self.branch = None
157 self.number = None
158 self.patchset = None
159 self.refspec = None
160 self.ref = None
161 self.oldrev = None
162 self.newrev = None
163
164 if event.change_number:
165 self.branch = event.branch
166 self.number = event.change_number
167 self.patchset = event.patch_number
168 self.refspec = event.refspec
169 if event.ref:
170 self.ref = event.ref
171 self.oldrev = event.oldrev
172 self.newrev = event.newrev
173
James E. Blairee743612012-05-29 14:49:32 -0700174 self.jobs = {}
175 self.job_urls = {}
176 self.change_ahead = None
177 self.change_behind = None
178 self.running_builds = []
179
James E. Blair32663402012-06-01 10:04:18 -0700180 def _id(self):
181 if self.number:
182 return '%s,%s' % (self.number, self.patchset)
183 return self.newrev
184
James E. Blairee743612012-05-29 14:49:32 -0700185 def __str__(self):
James E. Blair32663402012-06-01 10:04:18 -0700186 return '<Change 0x%x %s>' % (id(self), self._id())
James E. Blairee743612012-05-29 14:49:32 -0700187
188 def formatStatus(self, indent=0):
James E. Blair1e8dd892012-05-30 09:15:05 -0700189 indent_str = ' ' * indent
James E. Blairee743612012-05-29 14:49:32 -0700190 ret = ''
James E. Blair32663402012-06-01 10:04:18 -0700191 ret += '%sProject %s change %s\n' % (indent_str,
192 self.project.name,
193 self._id())
James E. Blairee743612012-05-29 14:49:32 -0700194 for job in self.project.getJobs(self.queue_name):
195 result = self.jobs.get(job.name)
196 ret += '%s %s: %s\n' % (indent_str, job.name, result)
197 if self.change_ahead:
198 ret += '%sWaiting on:\n' % (indent_str)
James E. Blair1e8dd892012-05-30 09:15:05 -0700199 ret += self.change_ahead.formatStatus(indent + 2)
James E. Blairee743612012-05-29 14:49:32 -0700200 return ret
201
202 def formatReport(self):
203 ret = ''
204 if self.didAllJobsSucceed():
205 ret += 'Build successful\n\n'
206 else:
207 ret += 'Build failed\n\n'
James E. Blair1e8dd892012-05-30 09:15:05 -0700208
James E. Blairee743612012-05-29 14:49:32 -0700209 for job in self.project.getJobs(self.queue_name):
210 result = self.jobs.get(job.name)
211 url = self.job_urls.get(job.name, job.name)
212 ret += '- %s : %s\n' % (url, result)
213 return ret
214
215 def resetAllBuilds(self):
216 self.jobs = {}
217 self.job_urls = {}
218 self.running_builds = []
219
220 def addBuild(self, build):
221 self.running_builds.append(build)
222
223 def setResult(self, build):
224 self.running_builds.remove(build)
225 self.jobs[build.job.name] = build.result
James E. Blairff986a12012-05-30 14:56:51 -0700226 if build.url:
227 self.job_urls[build.job.name] = build.url
James E. Blairee743612012-05-29 14:49:32 -0700228 if build.result != 'SUCCESS':
229 # Get a JobTree from a Job so we can find only its dependent jobs
230 root = self.project.getJobTreeForQueue(self.queue_name)
231 tree = root.getJobTreeForJob(build.job)
232 for job in tree.getJobs():
233 self.jobs[job.name] = 'SKIPPED'
234
235 def _findJobsToRun(self, job_trees):
236 torun = []
237 for tree in job_trees:
238 job = tree.job
239 if job:
240 result = self.jobs.get(job.name, None)
241 else:
242 # This is a null job tree, run all of its jobs
243 result = 'SUCCESS'
244 if not result:
245 if job not in [b.job for b in self.running_builds]:
246 torun.append(job)
247 elif result == 'SUCCESS':
248 torun.extend(self._findJobsToRun(tree.job_trees))
249 return torun
250
251 def findJobsToRun(self):
252 tree = self.project.getJobTreeForQueue(self.queue_name)
253 return self._findJobsToRun(tree.job_trees)
254
255 def areAllJobsComplete(self):
256 tree = self.project.getJobTreeForQueue(self.queue_name)
257 for job in tree.getJobs():
James E. Blair1e8dd892012-05-30 09:15:05 -0700258 if not job.name in self.jobs:
James E. Blairee743612012-05-29 14:49:32 -0700259 return False
260 return True
261
262 def didAllJobsSucceed(self):
263 for result in self.jobs.values():
264 if result != 'SUCCESS':
265 return False
266 return True
267
268 def delete(self):
269 if self.change_behind:
270 self.change_behind.change_ahead = None
271
James E. Blair1e8dd892012-05-30 09:15:05 -0700272
James E. Blairee743612012-05-29 14:49:32 -0700273class TriggerEvent(object):
274 def __init__(self):
275 self.data = None
James E. Blair32663402012-06-01 10:04:18 -0700276 # common
James E. Blairee743612012-05-29 14:49:32 -0700277 self.type = None
278 self.project_name = None
James E. Blair32663402012-06-01 10:04:18 -0700279 # patchset-created, comment-added, etc.
James E. Blairee743612012-05-29 14:49:32 -0700280 self.change_number = None
281 self.patch_number = None
James E. Blaira03262c2012-05-30 09:41:16 -0700282 self.refspec = None
James E. Blairee743612012-05-29 14:49:32 -0700283 self.approvals = []
284 self.branch = None
James E. Blair32663402012-06-01 10:04:18 -0700285 # ref-updated
James E. Blairee743612012-05-29 14:49:32 -0700286 self.ref = None
James E. Blair32663402012-06-01 10:04:18 -0700287 self.oldrev = None
288 self.newrew = None
James E. Blairee743612012-05-29 14:49:32 -0700289
290 def __str__(self):
291 ret = '<TriggerEvent %s %s' % (self.type, self.project_name)
James E. Blair1e8dd892012-05-30 09:15:05 -0700292
James E. Blairee743612012-05-29 14:49:32 -0700293 if self.branch:
294 ret += " %s" % self.branch
295 if self.change_number:
296 ret += " %s,%s" % (self.change_number, self.patch_number)
297 if self.approvals:
James E. Blair1e8dd892012-05-30 09:15:05 -0700298 ret += ' ' + ', '.join(
299 ['%s:%s' % (a['type'], a['value']) for a in self.approvals])
James E. Blairee743612012-05-29 14:49:32 -0700300 ret += '>'
301
302 return ret
303
James E. Blair1e8dd892012-05-30 09:15:05 -0700304
James E. Blairee743612012-05-29 14:49:32 -0700305class EventFilter(object):
306 def __init__(self, types=[], branches=[], refs=[], approvals=[]):
307 self._types = types
308 self._branches = branches
309 self._refs = refs
310 self.types = [re.compile(x) for x in types]
311 self.branches = [re.compile(x) for x in branches]
312 self.refs = [re.compile(x) for x in refs]
313 self.approvals = approvals
314
315 def __str__(self):
316 ret = '<EventFilter'
James E. Blair1e8dd892012-05-30 09:15:05 -0700317
James E. Blairee743612012-05-29 14:49:32 -0700318 if self._types:
319 ret += ' types: %s' % ', '.join(self._types)
320 if self._branches:
321 ret += ' branches: %s' % ', '.join(self._branches)
322 if self._refs:
323 ret += ' refs: %s' % ', '.join(self._refs)
324 if self.approvals:
James E. Blair1e8dd892012-05-30 09:15:05 -0700325 ret += ' approvals: %s' % ', '.join(
326 ['%s:%s' % a for a in self.approvals.items()])
James E. Blairee743612012-05-29 14:49:32 -0700327 ret += '>'
328
329 return ret
330
331 def matches(self, event):
332 def normalizeCategory(name):
333 name = name.lower()
334 return re.sub(' ', '-', name)
335
336 # event types are ORed
337 matches_type = False
338 for etype in self.types:
339 if etype.match(event.type):
340 matches_type = True
341 if self.types and not matches_type:
342 return False
343
344 # branches are ORed
345 matches_branch = False
346 for branch in self.branches:
347 if branch.match(event.branch):
348 matches_branch = True
349 if self.branches and not matches_branch:
350 return False
351
352 # refs are ORed
353 matches_ref = False
354 for ref in self.refs:
355 if ref.match(event.ref):
356 matches_ref = True
357 if self.refs and not matches_ref:
358 return False
359
360 # approvals are ANDed
361 for category, value in self.approvals.items():
362 matches_approval = False
363 for eapproval in event.approvals:
364 if (normalizeCategory(eapproval['description']) == category and
365 int(eapproval['value']) == int(value)):
366 matches_approval = True
James E. Blair1e8dd892012-05-30 09:15:05 -0700367 if not matches_approval:
368 return False
James E. Blairee743612012-05-29 14:49:32 -0700369 return True