blob: 9d49efbdaf1e8474362941d6c3c88606bdc71f66 [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
James E. Blair9f9667e2012-06-12 17:51:08 -070027 def __repr__(self):
James E. Blairee743612012-05-29 14:49:32 -070028 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
Clark Boylanfc56df32012-06-28 15:25:57 -0700158 self.url = None
James E. Blair32663402012-06-01 10:04:18 -0700159 self.patchset = None
160 self.refspec = None
161 self.ref = None
162 self.oldrev = None
163 self.newrev = None
164
165 if event.change_number:
166 self.branch = event.branch
167 self.number = event.change_number
Clark Boylanfc56df32012-06-28 15:25:57 -0700168 self.url = event.change_url
James E. Blair32663402012-06-01 10:04:18 -0700169 self.patchset = event.patch_number
170 self.refspec = event.refspec
171 if event.ref:
172 self.ref = event.ref
173 self.oldrev = event.oldrev
174 self.newrev = event.newrev
175
James E. Blairee743612012-05-29 14:49:32 -0700176 self.jobs = {}
177 self.job_urls = {}
178 self.change_ahead = None
179 self.change_behind = None
180 self.running_builds = []
181
James E. Blair32663402012-06-01 10:04:18 -0700182 def _id(self):
183 if self.number:
184 return '%s,%s' % (self.number, self.patchset)
185 return self.newrev
186
James E. Blair9f9667e2012-06-12 17:51:08 -0700187 def __repr__(self):
James E. Blair32663402012-06-01 10:04:18 -0700188 return '<Change 0x%x %s>' % (id(self), self._id())
James E. Blairee743612012-05-29 14:49:32 -0700189
Clark Boylan69878832012-06-21 16:25:39 -0700190 def _getBuild(self, job):
191 # We don't gurantee that we'll keep track of builds, but
192 # we might happen to have one running; useful for status.
193 for build in self.running_builds:
194 if build.job == job:
195 return build
196 return None
197
198 def formatStatus(self, indent=0, html=False):
James E. Blair1e8dd892012-05-30 09:15:05 -0700199 indent_str = ' ' * indent
James E. Blairee743612012-05-29 14:49:32 -0700200 ret = ''
Clark Boylanfc56df32012-06-28 15:25:57 -0700201 if html and self.url is not None:
202 ret += '%sProject %s change <a href="%s">%s</a>\n' % (indent_str,
203 self.project.name,
204 self.url,
205 self._id())
206 else:
207 ret += '%sProject %s change %s\n' % (indent_str,
208 self.project.name,
209 self._id())
James E. Blairee743612012-05-29 14:49:32 -0700210 for job in self.project.getJobs(self.queue_name):
211 result = self.jobs.get(job.name)
Clark Boylan69878832012-06-21 16:25:39 -0700212 job_name = job.name
213 if html:
214 build = self._getBuild(job)
215 if build:
216 url = build.url
217 else:
218 url = self.job_urls.get(job.name, None)
219 if url is not None:
220 job_name = '<a href="%s">%s</a>' % (url, job_name)
221 ret += '%s %s: %s' % (indent_str, job_name, result)
222 ret += '\n'
James E. Blairee743612012-05-29 14:49:32 -0700223 if self.change_ahead:
224 ret += '%sWaiting on:\n' % (indent_str)
Clark Boylan69878832012-06-21 16:25:39 -0700225 ret += self.change_ahead.formatStatus(indent + 2, html)
James E. Blairee743612012-05-29 14:49:32 -0700226 return ret
227
228 def formatReport(self):
229 ret = ''
230 if self.didAllJobsSucceed():
231 ret += 'Build successful\n\n'
232 else:
233 ret += 'Build failed\n\n'
James E. Blair1e8dd892012-05-30 09:15:05 -0700234
James E. Blairee743612012-05-29 14:49:32 -0700235 for job in self.project.getJobs(self.queue_name):
236 result = self.jobs.get(job.name)
237 url = self.job_urls.get(job.name, job.name)
238 ret += '- %s : %s\n' % (url, result)
239 return ret
240
241 def resetAllBuilds(self):
242 self.jobs = {}
243 self.job_urls = {}
244 self.running_builds = []
245
246 def addBuild(self, build):
247 self.running_builds.append(build)
248
249 def setResult(self, build):
250 self.running_builds.remove(build)
251 self.jobs[build.job.name] = build.result
James E. Blairff986a12012-05-30 14:56:51 -0700252 if build.url:
253 self.job_urls[build.job.name] = build.url
James E. Blairee743612012-05-29 14:49:32 -0700254 if build.result != 'SUCCESS':
255 # Get a JobTree from a Job so we can find only its dependent jobs
256 root = self.project.getJobTreeForQueue(self.queue_name)
257 tree = root.getJobTreeForJob(build.job)
258 for job in tree.getJobs():
259 self.jobs[job.name] = 'SKIPPED'
260
261 def _findJobsToRun(self, job_trees):
262 torun = []
263 for tree in job_trees:
264 job = tree.job
265 if job:
266 result = self.jobs.get(job.name, None)
267 else:
268 # This is a null job tree, run all of its jobs
269 result = 'SUCCESS'
270 if not result:
271 if job not in [b.job for b in self.running_builds]:
272 torun.append(job)
273 elif result == 'SUCCESS':
274 torun.extend(self._findJobsToRun(tree.job_trees))
275 return torun
276
277 def findJobsToRun(self):
278 tree = self.project.getJobTreeForQueue(self.queue_name)
James E. Blair9d43ac12012-06-04 14:15:42 -0700279 if not tree:
280 return []
James E. Blairee743612012-05-29 14:49:32 -0700281 return self._findJobsToRun(tree.job_trees)
282
283 def areAllJobsComplete(self):
284 tree = self.project.getJobTreeForQueue(self.queue_name)
285 for job in tree.getJobs():
James E. Blair1e8dd892012-05-30 09:15:05 -0700286 if not job.name in self.jobs:
James E. Blairee743612012-05-29 14:49:32 -0700287 return False
288 return True
289
290 def didAllJobsSucceed(self):
291 for result in self.jobs.values():
292 if result != 'SUCCESS':
293 return False
294 return True
295
296 def delete(self):
297 if self.change_behind:
298 self.change_behind.change_ahead = None
299
James E. Blair1e8dd892012-05-30 09:15:05 -0700300
James E. Blairee743612012-05-29 14:49:32 -0700301class TriggerEvent(object):
302 def __init__(self):
303 self.data = None
James E. Blair32663402012-06-01 10:04:18 -0700304 # common
James E. Blairee743612012-05-29 14:49:32 -0700305 self.type = None
306 self.project_name = None
James E. Blair32663402012-06-01 10:04:18 -0700307 # patchset-created, comment-added, etc.
James E. Blairee743612012-05-29 14:49:32 -0700308 self.change_number = None
Clark Boylanfc56df32012-06-28 15:25:57 -0700309 self.change_url = None
James E. Blairee743612012-05-29 14:49:32 -0700310 self.patch_number = None
James E. Blaira03262c2012-05-30 09:41:16 -0700311 self.refspec = None
James E. Blairee743612012-05-29 14:49:32 -0700312 self.approvals = []
313 self.branch = None
James E. Blair32663402012-06-01 10:04:18 -0700314 # ref-updated
James E. Blairee743612012-05-29 14:49:32 -0700315 self.ref = None
James E. Blair32663402012-06-01 10:04:18 -0700316 self.oldrev = None
317 self.newrew = None
James E. Blairee743612012-05-29 14:49:32 -0700318
James E. Blair9f9667e2012-06-12 17:51:08 -0700319 def __repr__(self):
James E. Blairee743612012-05-29 14:49:32 -0700320 ret = '<TriggerEvent %s %s' % (self.type, self.project_name)
James E. Blair1e8dd892012-05-30 09:15:05 -0700321
James E. Blairee743612012-05-29 14:49:32 -0700322 if self.branch:
323 ret += " %s" % self.branch
324 if self.change_number:
325 ret += " %s,%s" % (self.change_number, self.patch_number)
326 if self.approvals:
James E. Blair1e8dd892012-05-30 09:15:05 -0700327 ret += ' ' + ', '.join(
328 ['%s:%s' % (a['type'], a['value']) for a in self.approvals])
James E. Blairee743612012-05-29 14:49:32 -0700329 ret += '>'
330
331 return ret
332
James E. Blair1e8dd892012-05-30 09:15:05 -0700333
James E. Blairee743612012-05-29 14:49:32 -0700334class EventFilter(object):
335 def __init__(self, types=[], branches=[], refs=[], approvals=[]):
336 self._types = types
337 self._branches = branches
338 self._refs = refs
339 self.types = [re.compile(x) for x in types]
340 self.branches = [re.compile(x) for x in branches]
341 self.refs = [re.compile(x) for x in refs]
342 self.approvals = approvals
343
James E. Blair9f9667e2012-06-12 17:51:08 -0700344 def __repr__(self):
James E. Blairee743612012-05-29 14:49:32 -0700345 ret = '<EventFilter'
James E. Blair1e8dd892012-05-30 09:15:05 -0700346
James E. Blairee743612012-05-29 14:49:32 -0700347 if self._types:
348 ret += ' types: %s' % ', '.join(self._types)
349 if self._branches:
350 ret += ' branches: %s' % ', '.join(self._branches)
351 if self._refs:
352 ret += ' refs: %s' % ', '.join(self._refs)
353 if self.approvals:
James E. Blair1e8dd892012-05-30 09:15:05 -0700354 ret += ' approvals: %s' % ', '.join(
355 ['%s:%s' % a for a in self.approvals.items()])
James E. Blairee743612012-05-29 14:49:32 -0700356 ret += '>'
357
358 return ret
359
360 def matches(self, event):
361 def normalizeCategory(name):
362 name = name.lower()
363 return re.sub(' ', '-', name)
364
365 # event types are ORed
366 matches_type = False
367 for etype in self.types:
368 if etype.match(event.type):
369 matches_type = True
370 if self.types and not matches_type:
371 return False
372
373 # branches are ORed
374 matches_branch = False
375 for branch in self.branches:
376 if branch.match(event.branch):
377 matches_branch = True
378 if self.branches and not matches_branch:
379 return False
380
381 # refs are ORed
382 matches_ref = False
383 for ref in self.refs:
384 if ref.match(event.ref):
385 matches_ref = True
386 if self.refs and not matches_ref:
387 return False
388
389 # approvals are ANDed
390 for category, value in self.approvals.items():
391 matches_approval = False
392 for eapproval in event.approvals:
393 if (normalizeCategory(eapproval['description']) == category and
394 int(eapproval['value']) == int(value)):
395 matches_approval = True
James E. Blair1e8dd892012-05-30 09:15:05 -0700396 if not matches_approval:
397 return False
James E. Blairee743612012-05-29 14:49:32 -0700398 return True