blob: 329d0c42ecf8ea27cd10171d238ee9facb4480d6 [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
James E. Blairee743612012-05-29 14:49:32 -070015import logging
Zhongyue Luo1c860d72012-07-19 11:03:56 +080016import os
James E. Blair5d5bc2b2012-07-06 10:24:01 -070017import pickle
Zhongyue Luo1c860d72012-07-19 11:03:56 +080018import Queue
19import re
20import threading
21import yaml
James E. Blairee743612012-05-29 14:49:32 -070022
James E. Blair4886cc12012-07-18 15:39:41 -070023import model
James E. Blair4aea70c2012-07-26 14:23:24 -070024from model import Pipeline, Job, Project, ChangeQueue, EventFilter
James E. Blair4886cc12012-07-18 15:39:41 -070025import merger
James E. Blairee743612012-05-29 14:49:32 -070026
James E. Blair1e8dd892012-05-30 09:15:05 -070027
James E. Blaire9d45c32012-05-31 09:56:45 -070028class Scheduler(threading.Thread):
James E. Blairee743612012-05-29 14:49:32 -070029 log = logging.getLogger("zuul.Scheduler")
30
James E. Blaire9d45c32012-05-31 09:56:45 -070031 def __init__(self):
32 threading.Thread.__init__(self)
James E. Blairee743612012-05-29 14:49:32 -070033 self.wake_event = threading.Event()
James E. Blaire9d45c32012-05-31 09:56:45 -070034 self.reconfigure_complete_event = threading.Event()
James E. Blairb0fcae42012-07-17 11:12:10 -070035 self.queue_lock = threading.Lock()
James E. Blair5d5bc2b2012-07-06 10:24:01 -070036 self._pause = False
37 self._reconfigure = False
38 self._exit = False
James E. Blairb0fcae42012-07-17 11:12:10 -070039 self._stopped = False
James E. Blairee743612012-05-29 14:49:32 -070040 self.launcher = None
41 self.trigger = None
42
43 self.trigger_event_queue = Queue.Queue()
44 self.result_event_queue = Queue.Queue()
James E. Blaire9d45c32012-05-31 09:56:45 -070045 self._init()
James E. Blairee743612012-05-29 14:49:32 -070046
James E. Blaire9d45c32012-05-31 09:56:45 -070047 def _init(self):
James E. Blair4aea70c2012-07-26 14:23:24 -070048 self.pipelines = {}
James E. Blaire9d45c32012-05-31 09:56:45 -070049 self.jobs = {}
50 self.projects = {}
James E. Blairb0954652012-06-01 11:32:01 -070051 self.metajobs = {}
James E. Blairee743612012-05-29 14:49:32 -070052
James E. Blairb0fcae42012-07-17 11:12:10 -070053 def stop(self):
54 self._stopped = True
55 self.wake_event.set()
56
James E. Blaire5a847f2012-07-10 15:29:14 -070057 def _parseConfig(self, config_path):
James E. Blairee743612012-05-29 14:49:32 -070058 def toList(item):
James E. Blair1e8dd892012-05-30 09:15:05 -070059 if not item:
60 return []
James E. Blair32663402012-06-01 10:04:18 -070061 if isinstance(item, list):
James E. Blairee743612012-05-29 14:49:32 -070062 return item
63 return [item]
64
James E. Blaire5a847f2012-07-10 15:29:14 -070065 if config_path:
66 config_path = os.path.expanduser(config_path)
67 if not os.path.exists(config_path):
68 raise Exception("Unable to read layout config file at %s" %
69 config_path)
70 config_file = open(config_path)
71 data = yaml.load(config_file)
72
73 self._config_env = {}
74 for include in data.get('includes', []):
75 if 'python-file' in include:
76 fn = include['python-file']
77 if not os.path.isabs(fn):
78 base = os.path.dirname(config_path)
79 fn = os.path.join(base, fn)
80 fn = os.path.expanduser(fn)
81 execfile(fn, self._config_env)
James E. Blair1e8dd892012-05-30 09:15:05 -070082
James E. Blair4aea70c2012-07-26 14:23:24 -070083 for conf_pipeline in data.get('pipelines', []):
84 pipeline = Pipeline(conf_pipeline['name'])
85 manager = globals()[conf_pipeline['manager']](self, pipeline)
86 pipeline.setManager(manager)
87
88 self.pipelines[conf_pipeline['name']] = pipeline
89 manager.success_action = conf_pipeline.get('success')
90 manager.failure_action = conf_pipeline.get('failure')
91 manager.start_action = conf_pipeline.get('start')
92 for trigger in toList(conf_pipeline['trigger']):
James E. Blairee743612012-05-29 14:49:32 -070093 approvals = {}
94 for approval_dict in toList(trigger.get('approval')):
95 for k, v in approval_dict.items():
James E. Blair1e8dd892012-05-30 09:15:05 -070096 approvals[k] = v
James E. Blairee743612012-05-29 14:49:32 -070097 f = EventFilter(types=toList(trigger['event']),
98 branches=toList(trigger.get('branch')),
99 refs=toList(trigger.get('ref')),
Clark Boylanb9bcb402012-06-29 17:44:05 -0700100 approvals=approvals,
101 comment_filters=toList(
James E. Blair4aea70c2012-07-26 14:23:24 -0700102 trigger.get('comment_filter')))
James E. Blairee743612012-05-29 14:49:32 -0700103 manager.event_filters.append(f)
104
105 for config_job in data['jobs']:
106 job = self.getJob(config_job['name'])
James E. Blairb0954652012-06-01 11:32:01 -0700107 # Be careful to only set attributes explicitly present on
108 # this job, to avoid squashing attributes set by a meta-job.
109 m = config_job.get('failure-message', None)
110 if m:
111 job.failure_message = m
112 m = config_job.get('success-message', None)
113 if m:
114 job.success_message = m
James E. Blair222d4982012-07-16 09:31:19 -0700115 m = config_job.get('hold-following-changes', False)
116 if m:
117 job.hold_following_changes = True
James E. Blair4ec821f2012-08-23 15:28:28 -0700118 m = config_job.get('voting', None)
119 if m is not None:
120 job.voting = m
James E. Blaire5a847f2012-07-10 15:29:14 -0700121 fname = config_job.get('parameter-function', None)
122 if fname:
123 func = self._config_env.get(fname, None)
124 if not func:
125 raise Exception("Unable to find function %s" % fname)
126 job.parameter_function = func
James E. Blairee743612012-05-29 14:49:32 -0700127 branches = toList(config_job.get('branch'))
128 if branches:
James E. Blaire421a232012-07-25 16:59:21 -0700129 job._branches = branches
130 job.branches = [re.compile(x) for x in branches]
James E. Blairee743612012-05-29 14:49:32 -0700131
132 def add_jobs(job_tree, config_jobs):
133 for job in config_jobs:
134 if isinstance(job, list):
135 for x in job:
136 add_jobs(job_tree, x)
137 if isinstance(job, dict):
138 for parent, children in job.items():
139 parent_tree = job_tree.addJob(self.getJob(parent))
140 add_jobs(parent_tree, children)
141 if isinstance(job, str):
142 job_tree.addJob(self.getJob(job))
143
144 for config_project in data['projects']:
145 project = Project(config_project['name'])
146 self.projects[config_project['name']] = project
James E. Blair4886cc12012-07-18 15:39:41 -0700147 mode = config_project.get('merge-mode')
148 if mode and mode == 'cherry-pick':
149 project.merge_mode = model.CHERRY_PICK
James E. Blair4aea70c2012-07-26 14:23:24 -0700150 for pipeline in self.pipelines.values():
151 if pipeline.name in config_project:
152 job_tree = pipeline.addProject(project)
153 config_jobs = config_project[pipeline.name]
James E. Blairee743612012-05-29 14:49:32 -0700154 add_jobs(job_tree, config_jobs)
James E. Blairee743612012-05-29 14:49:32 -0700155
James E. Blairb0954652012-06-01 11:32:01 -0700156 # All jobs should be defined at this point, get rid of
157 # metajobs so that getJob isn't doing anything weird.
158 self.metajobs = {}
159
James E. Blairee743612012-05-29 14:49:32 -0700160 # TODO(jeblair): check that we don't end up with jobs like
161 # "foo - bar" because a ':' is missing in the yaml for a dependent job
James E. Blair4aea70c2012-07-26 14:23:24 -0700162 for pipeline in self.pipelines.values():
163 pipeline.manager._postConfig()
James E. Blairee743612012-05-29 14:49:32 -0700164
James E. Blair4886cc12012-07-18 15:39:41 -0700165 if self.config.has_option('zuul', 'git_dir'):
166 merge_root = self.config.get('zuul', 'git_dir')
167 else:
168 merge_root = '/var/lib/zuul/git'
James E. Blairceabcbc2012-08-17 13:48:46 -0700169 if self.config.has_option('zuul', 'push_change_refs'):
170 push_refs = self.config.getboolean('zuul', 'push_change_refs')
171 else:
172 push_refs = False
173 self.merger = merger.Merger(self.trigger, merge_root, push_refs)
James E. Blair4886cc12012-07-18 15:39:41 -0700174 for project in self.projects.values():
175 url = self.trigger.getGitUrl(project)
176 self.merger.addProject(project, url)
177
James E. Blairee743612012-05-29 14:49:32 -0700178 def getJob(self, name):
James E. Blair1e8dd892012-05-30 09:15:05 -0700179 if name in self.jobs:
James E. Blairee743612012-05-29 14:49:32 -0700180 return self.jobs[name]
181 job = Job(name)
James E. Blairb0954652012-06-01 11:32:01 -0700182 if name.startswith('^'):
183 # This is a meta-job
184 regex = re.compile(name)
185 self.metajobs[regex] = job
186 else:
187 # Apply attributes from matching meta-jobs
188 for regex, metajob in self.metajobs.items():
189 if regex.match(name):
190 job.copy(metajob)
191 self.jobs[name] = job
James E. Blairee743612012-05-29 14:49:32 -0700192 return job
193
194 def setLauncher(self, launcher):
195 self.launcher = launcher
196
197 def setTrigger(self, trigger):
198 self.trigger = trigger
199
200 def addEvent(self, event):
201 self.log.debug("Adding trigger event: %s" % event)
James E. Blairb0fcae42012-07-17 11:12:10 -0700202 self.queue_lock.acquire()
James E. Blairee743612012-05-29 14:49:32 -0700203 self.trigger_event_queue.put(event)
James E. Blairb0fcae42012-07-17 11:12:10 -0700204 self.queue_lock.release()
James E. Blairee743612012-05-29 14:49:32 -0700205 self.wake_event.set()
206
James E. Blair11700c32012-07-05 17:50:05 -0700207 def onBuildStarted(self, build):
208 self.log.debug("Adding start event for build: %s" % build)
James E. Blairb0fcae42012-07-17 11:12:10 -0700209 self.queue_lock.acquire()
James E. Blair11700c32012-07-05 17:50:05 -0700210 self.result_event_queue.put(('started', build))
James E. Blairb0fcae42012-07-17 11:12:10 -0700211 self.queue_lock.release()
James E. Blair11700c32012-07-05 17:50:05 -0700212 self.wake_event.set()
213
James E. Blairee743612012-05-29 14:49:32 -0700214 def onBuildCompleted(self, build):
James E. Blair11700c32012-07-05 17:50:05 -0700215 self.log.debug("Adding complete event for build: %s" % build)
James E. Blairb0fcae42012-07-17 11:12:10 -0700216 self.queue_lock.acquire()
James E. Blair11700c32012-07-05 17:50:05 -0700217 self.result_event_queue.put(('completed', build))
James E. Blairb0fcae42012-07-17 11:12:10 -0700218 self.queue_lock.release()
James E. Blairee743612012-05-29 14:49:32 -0700219 self.wake_event.set()
220
James E. Blaire9d45c32012-05-31 09:56:45 -0700221 def reconfigure(self, config):
James E. Blair5d5bc2b2012-07-06 10:24:01 -0700222 self.log.debug("Prepare to reconfigure")
James E. Blaire9d45c32012-05-31 09:56:45 -0700223 self.config = config
James E. Blair5d5bc2b2012-07-06 10:24:01 -0700224 self._pause = True
225 self._reconfigure = True
James E. Blaire9d45c32012-05-31 09:56:45 -0700226 self.wake_event.set()
227 self.log.debug("Waiting for reconfiguration")
228 self.reconfigure_complete_event.wait()
229 self.reconfigure_complete_event.clear()
230 self.log.debug("Reconfiguration complete")
231
James E. Blair5d5bc2b2012-07-06 10:24:01 -0700232 def exit(self):
233 self.log.debug("Prepare to exit")
234 self._pause = True
235 self._exit = True
236 self.wake_event.set()
237 self.log.debug("Waiting for exit")
238
239 def _get_queue_pickle_file(self):
James E. Blair5a95c862012-07-09 15:11:17 -0700240 if self.config.has_option('zuul', 'state_dir'):
241 state_dir = os.path.expanduser(self.config.get('zuul',
242 'state_dir'))
243 else:
244 state_dir = '/var/lib/zuul'
James E. Blair5d5bc2b2012-07-06 10:24:01 -0700245 return os.path.join(state_dir, 'queue.pickle')
246
247 def _save_queue(self):
248 pickle_file = self._get_queue_pickle_file()
249 events = []
250 while not self.trigger_event_queue.empty():
251 events.append(self.trigger_event_queue.get())
252 self.log.debug("Queue length is %s" % len(events))
253 if events:
254 self.log.debug("Saving queue")
255 pickle.dump(events, open(pickle_file, 'wb'))
256
257 def _load_queue(self):
258 pickle_file = self._get_queue_pickle_file()
259 if os.path.exists(pickle_file):
260 self.log.debug("Loading queue")
261 events = pickle.load(open(pickle_file, 'rb'))
262 self.log.debug("Queue length is %s" % len(events))
263 for event in events:
264 self.trigger_event_queue.put(event)
265 else:
266 self.log.debug("No queue file found")
267
268 def _delete_queue(self):
269 pickle_file = self._get_queue_pickle_file()
270 if os.path.exists(pickle_file):
271 self.log.debug("Deleting saved queue")
272 os.unlink(pickle_file)
273
274 def resume(self):
275 try:
276 self._load_queue()
277 except:
278 self.log.exception("Unable to load queue")
279 try:
280 self._delete_queue()
281 except:
282 self.log.exception("Unable to delete saved queue")
283 self.log.debug("Resuming queue processing")
284 self.wake_event.set()
285
286 def _doPauseEvent(self):
287 if self._exit:
288 self.log.debug("Exiting")
289 self._save_queue()
290 os._exit(0)
291 if self._reconfigure:
292 self.log.debug("Performing reconfiguration")
293 self._init()
294 self._parseConfig(self.config.get('zuul', 'layout_config'))
295 self._pause = False
James E. Blaire0487072012-08-29 17:38:31 -0700296 self._reconfigure = False
James E. Blair5d5bc2b2012-07-06 10:24:01 -0700297 self.reconfigure_complete_event.set()
James E. Blaire9d45c32012-05-31 09:56:45 -0700298
299 def _areAllBuildsComplete(self):
300 self.log.debug("Checking if all builds are complete")
301 waiting = False
James E. Blair4aea70c2012-07-26 14:23:24 -0700302 for pipeline in self.pipelines.values():
303 for build in pipeline.manager.building_jobs.keys():
304 self.log.debug("%s waiting on %s" % (pipeline.manager, build))
James E. Blaire9d45c32012-05-31 09:56:45 -0700305 waiting = True
306 if not waiting:
307 self.log.debug("All builds are complete")
308 return True
309 self.log.debug("All builds are not complete")
310 return False
311
James E. Blairee743612012-05-29 14:49:32 -0700312 def run(self):
313 while True:
314 self.log.debug("Run handler sleeping")
315 self.wake_event.wait()
316 self.wake_event.clear()
James E. Blairb0fcae42012-07-17 11:12:10 -0700317 if self._stopped:
318 return
James E. Blairee743612012-05-29 14:49:32 -0700319 self.log.debug("Run handler awake")
James E. Blairb0fcae42012-07-17 11:12:10 -0700320 self.queue_lock.acquire()
James E. Blairee743612012-05-29 14:49:32 -0700321 try:
James E. Blair5d5bc2b2012-07-06 10:24:01 -0700322 if not self._pause:
James E. Blaire9d45c32012-05-31 09:56:45 -0700323 if not self.trigger_event_queue.empty():
324 self.process_event_queue()
325
James E. Blairee743612012-05-29 14:49:32 -0700326 if not self.result_event_queue.empty():
327 self.process_result_queue()
James E. Blaire9d45c32012-05-31 09:56:45 -0700328
James E. Blair5d5bc2b2012-07-06 10:24:01 -0700329 if self._pause and self._areAllBuildsComplete():
330 self._doPauseEvent()
James E. Blaire9d45c32012-05-31 09:56:45 -0700331
James E. Blair5d5bc2b2012-07-06 10:24:01 -0700332 if not self._pause:
James E. Blair4baa94c2012-06-07 17:04:21 -0700333 if not (self.trigger_event_queue.empty() and
334 self.result_event_queue.empty()):
335 self.wake_event.set()
336 else:
337 if not self.result_event_queue.empty():
338 self.wake_event.set()
James E. Blairee743612012-05-29 14:49:32 -0700339 except:
340 self.log.exception("Exception in run handler:")
James E. Blairb0fcae42012-07-17 11:12:10 -0700341 self.queue_lock.release()
James E. Blairee743612012-05-29 14:49:32 -0700342
343 def process_event_queue(self):
344 self.log.debug("Fetching trigger event")
345 event = self.trigger_event_queue.get()
346 self.log.debug("Processing trigger event %s" % event)
347 project = self.projects.get(event.project_name)
348 if not project:
349 self.log.warning("Project %s not found" % event.project_name)
350 return
351
James E. Blair4aea70c2012-07-26 14:23:24 -0700352 for pipeline in self.pipelines.values():
353 if not pipeline.manager.eventMatches(event):
354 self.log.debug("Event %s ignored by %s" % (event, pipeline))
James E. Blairee743612012-05-29 14:49:32 -0700355 continue
James E. Blair4aea70c2012-07-26 14:23:24 -0700356 change = event.getChange(project, self.trigger)
James E. Blaire421a232012-07-25 16:59:21 -0700357 self.log.info("Adding %s, %s to %s" %
James E. Blair4aea70c2012-07-26 14:23:24 -0700358 (project, change, pipeline))
359 pipeline.manager.addChange(change)
James E. Blair1e8dd892012-05-30 09:15:05 -0700360
James E. Blairee743612012-05-29 14:49:32 -0700361 def process_result_queue(self):
362 self.log.debug("Fetching result event")
James E. Blair11700c32012-07-05 17:50:05 -0700363 event_type, build = self.result_event_queue.get()
James E. Blairee743612012-05-29 14:49:32 -0700364 self.log.debug("Processing result event %s" % build)
James E. Blair4aea70c2012-07-26 14:23:24 -0700365 for pipeline in self.pipelines.values():
James E. Blair11700c32012-07-05 17:50:05 -0700366 if event_type == 'started':
James E. Blair4aea70c2012-07-26 14:23:24 -0700367 if pipeline.manager.onBuildStarted(build):
James E. Blair11700c32012-07-05 17:50:05 -0700368 return
369 elif event_type == 'completed':
James E. Blair4aea70c2012-07-26 14:23:24 -0700370 if pipeline.manager.onBuildCompleted(build):
James E. Blair11700c32012-07-05 17:50:05 -0700371 return
James E. Blairc84dd262012-05-31 10:03:13 -0700372 self.log.warning("Build %s not found by any queue manager" % (build))
James E. Blairee743612012-05-29 14:49:32 -0700373
James E. Blair268d9342012-06-13 18:24:29 -0700374 def formatStatusHTML(self):
375 ret = '<html><pre>'
James E. Blaire0487072012-08-29 17:38:31 -0700376 if self._pause:
377 ret += '<p><b>Queue only mode:</b> preparing to '
378 if self._reconfigure:
379 ret += 'reconfigure'
380 if self._exit:
381 ret += 'exit'
382 ret += ', queue length: %s' % self.trigger_event_queue.qsize()
383 ret += '</p>'
384
James E. Blair4aea70c2012-07-26 14:23:24 -0700385 keys = self.pipelines.keys()
James E. Blair268d9342012-06-13 18:24:29 -0700386 keys.sort()
387 for key in keys:
James E. Blair4aea70c2012-07-26 14:23:24 -0700388 pipeline = self.pipelines[key]
389 s = 'Pipeline: %s' % pipeline.name
James E. Blair268d9342012-06-13 18:24:29 -0700390 ret += s + '\n'
391 ret += '-' * len(s) + '\n'
James E. Blaire0487072012-08-29 17:38:31 -0700392 ret += pipeline.formatStatusHTML()
James E. Blair268d9342012-06-13 18:24:29 -0700393 ret += '\n'
394 ret += '</pre></html>'
395 return ret
396
James E. Blair1e8dd892012-05-30 09:15:05 -0700397
James E. Blair4aea70c2012-07-26 14:23:24 -0700398class BasePipelineManager(object):
399 log = logging.getLogger("zuul.BasePipelineManager")
James E. Blairee743612012-05-29 14:49:32 -0700400
James E. Blair4aea70c2012-07-26 14:23:24 -0700401 def __init__(self, sched, pipeline):
James E. Blairee743612012-05-29 14:49:32 -0700402 self.sched = sched
James E. Blair4aea70c2012-07-26 14:23:24 -0700403 self.pipeline = pipeline
James E. Blairee743612012-05-29 14:49:32 -0700404 self.building_jobs = {}
405 self.event_filters = []
406 self.success_action = {}
407 self.failure_action = {}
James E. Blairdc253862012-06-13 17:12:42 -0700408 self.start_action = {}
James E. Blairee743612012-05-29 14:49:32 -0700409
410 def __str__(self):
James E. Blair93cc8d42012-08-07 10:46:51 -0700411 return "<%s %s>" % (self.__class__.__name__, self.pipeline.name)
James E. Blairee743612012-05-29 14:49:32 -0700412
413 def _postConfig(self):
James E. Blair4aea70c2012-07-26 14:23:24 -0700414 self.log.info("Configured Pipeline Manager %s" % self.pipeline.name)
James E. Blairee743612012-05-29 14:49:32 -0700415 self.log.info(" Events:")
416 for e in self.event_filters:
417 self.log.info(" %s" % e)
418 self.log.info(" Projects:")
James E. Blair1e8dd892012-05-30 09:15:05 -0700419
James E. Blairee743612012-05-29 14:49:32 -0700420 def log_jobs(tree, indent=0):
James E. Blair1e8dd892012-05-30 09:15:05 -0700421 istr = ' ' + ' ' * indent
James E. Blairee743612012-05-29 14:49:32 -0700422 if tree.job:
423 efilters = ''
James E. Blaire421a232012-07-25 16:59:21 -0700424 for b in tree.job._branches:
425 efilters += str(b)
James E. Blairee743612012-05-29 14:49:32 -0700426 if efilters:
James E. Blair1e8dd892012-05-30 09:15:05 -0700427 efilters = ' ' + efilters
James E. Blair222d4982012-07-16 09:31:19 -0700428 hold = ''
429 if tree.job.hold_following_changes:
430 hold = ' [hold]'
James E. Blair4ec821f2012-08-23 15:28:28 -0700431 voting = ''
432 if not tree.job.voting:
433 voting = ' [nonvoting]'
434 self.log.info("%s%s%s%s%s" % (istr, repr(tree.job),
435 efilters, hold, voting))
James E. Blairee743612012-05-29 14:49:32 -0700436 for x in tree.job_trees:
James E. Blair1e8dd892012-05-30 09:15:05 -0700437 log_jobs(x, indent + 2)
438
James E. Blairee743612012-05-29 14:49:32 -0700439 for p in self.sched.projects.values():
James E. Blair4aea70c2012-07-26 14:23:24 -0700440 tree = self.pipeline.getJobTree(p)
441 if tree:
James E. Blairee743612012-05-29 14:49:32 -0700442 self.log.info(" %s" % p)
James E. Blair4aea70c2012-07-26 14:23:24 -0700443 log_jobs(tree)
James E. Blairdc253862012-06-13 17:12:42 -0700444 if self.start_action:
445 self.log.info(" On start:")
446 self.log.info(" %s" % self.start_action)
James E. Blairee743612012-05-29 14:49:32 -0700447 if self.success_action:
448 self.log.info(" On success:")
449 self.log.info(" %s" % self.success_action)
450 if self.failure_action:
451 self.log.info(" On failure:")
452 self.log.info(" %s" % self.failure_action)
453
James E. Blaire421a232012-07-25 16:59:21 -0700454 def getSubmitAllowNeeds(self):
455 # Get a list of code review labels that are allowed to be
456 # "needed" in the submit records for a change, with respect
457 # to this queue. In other words, the list of review labels
458 # this queue itself is likely to set before submitting.
James E. Blair4aea70c2012-07-26 14:23:24 -0700459 if self.success_action:
460 return self.success_action.keys()
461 else:
462 return {}
James E. Blaire421a232012-07-25 16:59:21 -0700463
James E. Blairee743612012-05-29 14:49:32 -0700464 def eventMatches(self, event):
465 for ef in self.event_filters:
James E. Blairee743612012-05-29 14:49:32 -0700466 if ef.matches(event):
467 return True
468 return False
469
James E. Blair0dc8ba92012-07-16 14:23:52 -0700470 def isChangeAlreadyInQueue(self, change):
James E. Blaire0487072012-08-29 17:38:31 -0700471 for c in self.pipeline.getChangesInQueue():
James E. Blair0dc8ba92012-07-16 14:23:52 -0700472 if change.equals(c):
473 return True
474 return False
475
James E. Blaire0487072012-08-29 17:38:31 -0700476 def reportStart(self, change):
477 try:
478 self.log.info("Reporting start, action %s change %s" %
479 (self.start_action, change))
480 msg = "Starting %s jobs." % self.pipeline.name
481 ret = self.sched.trigger.report(change, msg, self.start_action)
482 if ret:
483 self.log.error("Reporting change start %s received: %s" %
484 (change, ret))
485 except:
486 self.log.exception("Exception while reporting start:")
487
488 def isChangeReadyToBeEnqueued(self, change):
489 return True
490
491 def enqueueChangesAhead(self, change):
492 return True
493
494 def enqueueChangesBehind(self, change):
495 return True
496
James E. Blairee743612012-05-29 14:49:32 -0700497 def addChange(self, change):
James E. Blaire0487072012-08-29 17:38:31 -0700498 self.log.debug("Considering adding change %s" % change)
James E. Blair0dc8ba92012-07-16 14:23:52 -0700499 if self.isChangeAlreadyInQueue(change):
500 self.log.debug("Change %s is already in queue, ignoring" % change)
James E. Blaire0487072012-08-29 17:38:31 -0700501 return True
James E. Blair692c6b32012-07-17 11:16:35 -0700502
James E. Blaire0487072012-08-29 17:38:31 -0700503 if not self.isChangeReadyToBeEnqueued(change):
504 self.log.debug("Change %s is not ready to be enqueued, ignoring" %
505 change)
506 return False
507
508 if not self.enqueueChangesAhead(change):
509 self.log.debug("Failed to enqueue changes ahead of" % change)
510 return False
511
512 if self.isChangeAlreadyInQueue(change):
513 self.log.debug("Change %s is already in queue, ignoring" % change)
514 return True
515
516 change_queue = self.pipeline.getQueue(change.project)
517 if change_queue:
518 self.log.debug("Adding change %s to queue %s" %
519 (change, change_queue))
520 if self.start_action:
521 self.reportStart(change)
522 change_queue.enqueueChange(change)
523 self.enqueueChangesBehind(change)
524 else:
525 self.log.error("Unable to find change queue for project %s" %
526 change.project)
527 return False
528 self.launchJobs()
James E. Blairee743612012-05-29 14:49:32 -0700529
James E. Blairdaabed22012-08-15 15:38:57 -0700530 def _launchJobs(self, change, jobs):
James E. Blairee743612012-05-29 14:49:32 -0700531 self.log.debug("Launching jobs for change %s" % change)
James E. Blair4886cc12012-07-18 15:39:41 -0700532 ref = change.current_build_set.getRef()
James E. Blairdaabed22012-08-15 15:38:57 -0700533 if hasattr(change, 'refspec') and not ref:
James E. Blair4886cc12012-07-18 15:39:41 -0700534 change.current_build_set.setConfiguration()
535 ref = change.current_build_set.getRef()
James E. Blair973721f2012-08-15 10:19:43 -0700536 merged = self.sched.merger.mergeChanges([change], ref,
537 mode=model.MERGE_IF_NECESSARY)
538 if not merged:
539 self.log.info("Unable to merge change %s" % change)
540 self.pipeline.setUnableToMerge(change)
541 self.possiblyReportChange(change)
542 return
James E. Blair4886cc12012-07-18 15:39:41 -0700543
James E. Blair4aea70c2012-07-26 14:23:24 -0700544 for job in self.pipeline.findJobsToRun(change):
James E. Blairee743612012-05-29 14:49:32 -0700545 self.log.debug("Found job %s for change %s" % (job, change))
546 try:
James E. Blair03b94ef2012-08-20 10:54:29 -0700547 build = self.sched.launcher.launch(job, change, self.pipeline)
James E. Blairee743612012-05-29 14:49:32 -0700548 self.building_jobs[build] = change
Zhongyue Luo1c860d72012-07-19 11:03:56 +0800549 self.log.debug("Adding build %s of job %s to change %s" %
550 (build, job, change))
James E. Blairee743612012-05-29 14:49:32 -0700551 change.addBuild(build)
552 except:
Zhongyue Luo1c860d72012-07-19 11:03:56 +0800553 self.log.exception("Exception while launching job %s "
554 "for change %s:" % (job, change))
James E. Blairee743612012-05-29 14:49:32 -0700555
James E. Blaire0487072012-08-29 17:38:31 -0700556 def launchJobs(self, change=None):
557 if not change:
558 for change in self.pipeline.getAllChanges():
559 self.launchJobs(change)
560 return
James E. Blairdaabed22012-08-15 15:38:57 -0700561 jobs = self.pipeline.findJobsToRun(change)
562 if jobs:
563 self._launchJobs(change, jobs)
564
James E. Blair11700c32012-07-05 17:50:05 -0700565 def updateBuildDescriptions(self, build_set):
566 for build in build_set.getBuilds():
James E. Blair8b0d4c42012-08-23 16:03:05 -0700567 desc = self.formatDescription(build)
James E. Blair11700c32012-07-05 17:50:05 -0700568 self.sched.launcher.setBuildDescription(build, desc)
569
570 if build_set.previous_build_set:
571 for build in build_set.previous_build_set.getBuilds():
James E. Blair8b0d4c42012-08-23 16:03:05 -0700572 desc = self.formatDescription(build)
James E. Blair11700c32012-07-05 17:50:05 -0700573 self.sched.launcher.setBuildDescription(build, desc)
574
575 def onBuildStarted(self, build):
576 self.log.debug("Build %s started" % build)
577 if build not in self.building_jobs:
578 self.log.debug("Build %s not found" % (build))
579 # Or triggered externally, or triggered before zuul started,
580 # or restarted
581 return False
582
583 self.updateBuildDescriptions(build.build_set)
584 return True
585
James E. Blaire0487072012-08-29 17:38:31 -0700586 def handleFailedChange(self, change):
587 pass
588
James E. Blairee743612012-05-29 14:49:32 -0700589 def onBuildCompleted(self, build):
590 self.log.debug("Build %s completed" % build)
James E. Blair1e8dd892012-05-30 09:15:05 -0700591 if build not in self.building_jobs:
James E. Blairc84dd262012-05-31 10:03:13 -0700592 self.log.debug("Build %s not found" % (build))
James E. Blairee743612012-05-29 14:49:32 -0700593 # Or triggered externally, or triggered before zuul started,
594 # or restarted
595 return False
596 change = self.building_jobs[build]
Zhongyue Luo1c860d72012-07-19 11:03:56 +0800597 self.log.debug("Found change %s which triggered completed build %s" %
598 (change, build))
James E. Blairee743612012-05-29 14:49:32 -0700599
600 del self.building_jobs[build]
601
James E. Blair4aea70c2012-07-26 14:23:24 -0700602 self.pipeline.setResult(change, build)
Zhongyue Luo1c860d72012-07-19 11:03:56 +0800603 self.log.info("Change %s status is now:\n %s" %
James E. Blaire0487072012-08-29 17:38:31 -0700604 (change, self.pipeline.formatStatus(change)))
James E. Blairee743612012-05-29 14:49:32 -0700605
James E. Blaire0487072012-08-29 17:38:31 -0700606 if self.pipeline.didAnyJobFail(change):
607 self.handleFailedChange(change)
James E. Blair11700c32012-07-05 17:50:05 -0700608
James E. Blaire0487072012-08-29 17:38:31 -0700609 self.reportChanges()
610 self.launchJobs()
James E. Blair11700c32012-07-05 17:50:05 -0700611 self.updateBuildDescriptions(build.build_set)
James E. Blairee743612012-05-29 14:49:32 -0700612 return True
613
James E. Blaire0487072012-08-29 17:38:31 -0700614 def reportChanges(self):
615 self.log.debug("Searching for changes to report")
616 reported = False
617 for change in self.pipeline.getAllChanges():
618 self.log.debug(" checking %s" % change)
619 if self.pipeline.areAllJobsComplete(change):
620 self.log.debug(" possibly reporting %s" % change)
621 if self.possiblyReportChange(change):
622 reported = True
623 if reported:
624 self.reportChanges()
625 self.log.debug("Done searching for changes to report")
626
James E. Blairee743612012-05-29 14:49:32 -0700627 def possiblyReportChange(self, change):
628 self.log.debug("Possibly reporting change %s" % change)
James E. Blaire0487072012-08-29 17:38:31 -0700629 # Even if a change isn't reportable, keep going so that it
630 # gets dequeued in the normal manner.
631 if change.is_reportable and change.reported:
632 self.log.debug("Change %s already reported" % change)
633 return False
634 change_ahead = change.change_ahead
635 if not change_ahead:
636 self.log.debug("Change %s is at the front of the queue, "
637 "reporting" % (change))
638 ret = self.reportChange(change)
639 if self.changes_merge:
640 succeeded = self.pipeline.didAllJobsSucceed(change)
641 merged = (not ret)
642 if merged:
643 merged = self.sched.trigger.isMerged(change, change.branch)
644 self.log.info("Reported change %s status: all-succeeded: %s, "
645 "merged: %s" % (change, succeeded, merged))
646 if not (succeeded and merged):
647 self.log.debug("Reported change %s failed tests or failed "
648 "to merge" % (change))
649 self.handleFailedChange(change)
650 return True
651 self.log.debug("Removing reported change %s from queue" %
652 change)
653 change_queue = self.pipeline.getQueue(change.project)
654 change_queue.dequeueChange(change)
655 return True
James E. Blairee743612012-05-29 14:49:32 -0700656
657 def reportChange(self, change):
James E. Blair4aea70c2012-07-26 14:23:24 -0700658 if not change.is_reportable:
659 return False
James E. Blair6173fb32012-07-11 17:23:33 -0700660 if change.reported:
James E. Blairb0fcae42012-07-17 11:12:10 -0700661 return 0
James E. Blair4aea70c2012-07-26 14:23:24 -0700662 self.log.debug("Reporting change %s" % change)
James E. Blairee743612012-05-29 14:49:32 -0700663 ret = None
James E. Blair4aea70c2012-07-26 14:23:24 -0700664 if self.pipeline.didAllJobsSucceed(change):
James E. Blairee743612012-05-29 14:49:32 -0700665 action = self.success_action
James E. Blair11700c32012-07-05 17:50:05 -0700666 change.setReportedResult('SUCCESS')
James E. Blairee743612012-05-29 14:49:32 -0700667 else:
668 action = self.failure_action
James E. Blair11700c32012-07-05 17:50:05 -0700669 change.setReportedResult('FAILURE')
James E. Blair8b0d4c42012-08-23 16:03:05 -0700670 report = self.formatReport(change)
James E. Blaire0487072012-08-29 17:38:31 -0700671 change.reported = True
James E. Blairee743612012-05-29 14:49:32 -0700672 try:
Zhongyue Luo1c860d72012-07-19 11:03:56 +0800673 self.log.info("Reporting change %s, action: %s" %
674 (change, action))
James E. Blair8b0d4c42012-08-23 16:03:05 -0700675 ret = self.sched.trigger.report(change, report, action)
James E. Blairee743612012-05-29 14:49:32 -0700676 if ret:
Zhongyue Luo1c860d72012-07-19 11:03:56 +0800677 self.log.error("Reporting change %s received: %s" %
678 (change, ret))
James E. Blairee743612012-05-29 14:49:32 -0700679 except:
680 self.log.exception("Exception while reporting:")
James E. Blair11700c32012-07-05 17:50:05 -0700681 change.setReportedResult('ERROR')
682 self.updateBuildDescriptions(change.current_build_set)
James E. Blairee743612012-05-29 14:49:32 -0700683 return ret
684
James E. Blair8b0d4c42012-08-23 16:03:05 -0700685 def formatReport(self, changeish):
686 ret = ''
687 if self.pipeline.didAllJobsSucceed(changeish):
688 ret += 'Build successful\n\n'
689 else:
690 ret += 'Build failed\n\n'
691
692 if changeish.dequeued_needing_change:
693 ret += "This change depends on a change that failed to merge."
694 elif changeish.current_build_set.unable_to_merge:
695 ret += "This change was unable to be automatically merged "\
696 "with the current state of the repository. Please "\
697 "rebase your change and upload a new patchset."
698 else:
James E. Blaira35fcce2012-08-24 10:46:01 -0700699 if self.sched.config.has_option('zuul', 'url_pattern'):
700 pattern = self.sched.config.get('zuul', 'url_pattern')
701 else:
702 pattern = None
James E. Blair8b0d4c42012-08-23 16:03:05 -0700703 for job in self.pipeline.getJobs(changeish):
704 build = changeish.current_build_set.getBuild(job.name)
705 result = build.result
706 if result == 'SUCCESS' and job.success_message:
707 result = job.success_message
708 elif result == 'FAILURE' and job.failure_message:
709 result = job.failure_message
James E. Blair7ee88a22012-09-12 18:59:31 +0200710 url = None
James E. Blair8b0d4c42012-08-23 16:03:05 -0700711 if build.url:
712 if pattern:
713 url = pattern.format(change=changeish,
714 pipeline=self.pipeline,
715 job=job,
716 build=build)
717 else:
718 url = build.url
719 if not url:
720 url = job.name
721 if not job.voting:
722 voting = ' (non-voting)'
723 else:
724 voting = ''
725 ret += '- %s : %s%s\n' % (url, result, voting)
726 return ret
727
728 def formatDescription(self, build):
729 concurrent_changes = ''
730 concurrent_builds = ''
731 other_builds = ''
732
733 for change in build.build_set.other_changes:
734 concurrent_changes += '<li><a href="{change.url}">\
735 {change.number},{change.patchset}</a></li>'.format(
736 change=change)
737
738 change = build.build_set.change
739
740 for build in build.build_set.getBuilds():
741 if build.base_url:
742 concurrent_builds += """\
743<li>
744 <a href="{build.base_url}">
745 {build.job.name} #{build.number}</a>: {build.result}
746</li>
747""".format(build=build)
748 else:
749 concurrent_builds += """\
750<li>
751 {build.job.name}: {build.result}
752</li>""".format(build=build)
753
754 if build.build_set.previous_build_set:
755 other_build = build.build_set.previous_build_set.getBuild(
756 build.job.name)
757 if other_build:
758 other_builds += """\
759<li>
760 Preceded by: <a href="{build.base_url}">
761 {build.job.name} #{build.number}</a>
762</li>
763""".format(build=other_build)
764
765 if build.build_set.next_build_set:
766 other_build = build.build_set.next_build_set.getBuild(
767 build.job.name)
768 if other_build:
769 other_builds += """\
770<li>
771 Succeeded by: <a href="{build.base_url}">
772 {build.job.name} #{build.number}</a>
773</li>
774""".format(build=other_build)
775
776 result = build.build_set.result
777
778 if hasattr(change, 'number'):
779 ret = """\
780<p>
781 Triggered by change:
782 <a href="{change.url}">{change.number},{change.patchset}</a><br/>
783 Branch: <b>{change.branch}</b><br/>
784 Pipeline: <b>{self.pipeline.name}</b>
785</p>"""
786 else:
787 ret = """\
788<p>
789 Triggered by reference:
790 {change.ref}</a><br/>
791 Old revision: <b>{change.oldrev}</b><br/>
792 New revision: <b>{change.newrev}</b><br/>
793 Pipeline: <b>{self.pipeline.name}</b>
794</p>"""
795
796 if concurrent_changes:
797 ret += """\
798<p>
799 Other changes tested concurrently with this change:
800 <ul>{concurrent_changes}</ul>
801</p>
802"""
803 if concurrent_builds:
804 ret += """\
805<p>
806 All builds for this change set:
807 <ul>{concurrent_builds}</ul>
808</p>
809"""
810
811 if other_builds:
812 ret += """\
813<p>
814 Other build sets for this change:
815 <ul>{other_builds}</ul>
816</p>
817"""
818 if result:
819 ret += """\
820<p>
821 Reported result: <b>{result}</b>
822</p>
823"""
824
825 ret = ret.format(**locals())
James E. Blair268d9342012-06-13 18:24:29 -0700826 return ret
827
James E. Blair1e8dd892012-05-30 09:15:05 -0700828
James E. Blair4aea70c2012-07-26 14:23:24 -0700829class IndependentPipelineManager(BasePipelineManager):
830 log = logging.getLogger("zuul.IndependentPipelineManager")
James E. Blaire0487072012-08-29 17:38:31 -0700831 changes_merge = False
832
833 def _postConfig(self):
834 super(IndependentPipelineManager, self)._postConfig()
835
836 change_queue = ChangeQueue(self.pipeline, dependent=False)
837 for project in self.pipeline.getProjects():
838 change_queue.addProject(project)
839
840 self.pipeline.addQueue(change_queue)
James E. Blairee743612012-05-29 14:49:32 -0700841
James E. Blair1e8dd892012-05-30 09:15:05 -0700842
James E. Blair4aea70c2012-07-26 14:23:24 -0700843class DependentPipelineManager(BasePipelineManager):
844 log = logging.getLogger("zuul.DependentPipelineManager")
James E. Blaire0487072012-08-29 17:38:31 -0700845 changes_merge = True
James E. Blairee743612012-05-29 14:49:32 -0700846
847 def __init__(self, *args, **kwargs):
James E. Blair4aea70c2012-07-26 14:23:24 -0700848 super(DependentPipelineManager, self).__init__(*args, **kwargs)
James E. Blairee743612012-05-29 14:49:32 -0700849
850 def _postConfig(self):
James E. Blair4aea70c2012-07-26 14:23:24 -0700851 super(DependentPipelineManager, self)._postConfig()
James E. Blairee743612012-05-29 14:49:32 -0700852 self.buildChangeQueues()
853
854 def buildChangeQueues(self):
855 self.log.debug("Building shared change queues")
856 change_queues = []
857
James E. Blair4aea70c2012-07-26 14:23:24 -0700858 for project in self.pipeline.getProjects():
859 change_queue = ChangeQueue(self.pipeline)
860 change_queue.addProject(project)
861 change_queues.append(change_queue)
862 self.log.debug("Created queue: %s" % change_queue)
James E. Blairee743612012-05-29 14:49:32 -0700863
864 self.log.debug("Combining shared queues")
865 new_change_queues = []
866 for a in change_queues:
867 merged_a = False
868 for b in new_change_queues:
869 if not a.getJobs().isdisjoint(b.getJobs()):
870 self.log.debug("Merging queue %s into %s" % (a, b))
871 b.mergeChangeQueue(a)
872 merged_a = True
873 break # this breaks out of 'for b' and continues 'for a'
874 if not merged_a:
875 self.log.debug("Keeping queue %s" % (a))
876 new_change_queues.append(a)
James E. Blair1e8dd892012-05-30 09:15:05 -0700877
James E. Blairee743612012-05-29 14:49:32 -0700878 self.log.info(" Shared change queues:")
James E. Blaire0487072012-08-29 17:38:31 -0700879 for queue in new_change_queues:
880 self.pipeline.addQueue(queue)
881 self.log.info(" %s" % queue)
James E. Blairee743612012-05-29 14:49:32 -0700882
James E. Blaire0487072012-08-29 17:38:31 -0700883 def isChangeReadyToBeEnqueued(self, change):
884 if not self.sched.trigger.canMerge(change,
885 self.getSubmitAllowNeeds()):
886 self.log.debug("Change %s can not merge, ignoring" % change)
887 return False
888 return True
James E. Blair1e8dd892012-05-30 09:15:05 -0700889
James E. Blaire0487072012-08-29 17:38:31 -0700890 def enqueueChangesBehind(self, change):
891 to_enqueue = []
892 self.log.debug("Checking for changes needing %s:" % change)
893 if not hasattr(change, 'needed_by_changes'):
894 self.log.debug(" Changeish does not support dependencies")
895 return
896 for needs in change.needed_by_changes:
897 if self.sched.trigger.canMerge(needs,
898 self.getSubmitAllowNeeds()):
899 self.log.debug(" Change %s needs %s and is ready to merge" %
900 (needs, change))
901 to_enqueue.append(needs)
902 if not to_enqueue:
903 self.log.debug(" No changes need %s" % change)
904
905 for other_change in to_enqueue:
906 self.addChange(other_change)
907
908 def enqueueChangesAhead(self, change):
909 ret = self.checkForChangesNeededBy(change)
910 if ret in [True, False]:
911 return ret
912 self.log.debug(" Change %s must be merged ahead of %s" %
913 (ret, change))
914 return self.addChange(ret)
915
916 def checkForChangesNeededBy(self, change):
James E. Blaire421a232012-07-25 16:59:21 -0700917 self.log.debug("Checking for changes needed by %s:" % change)
918 # Return true if okay to proceed enqueing this change,
919 # false if the change should not be enqueued.
James E. Blair4aea70c2012-07-26 14:23:24 -0700920 if not hasattr(change, 'needs_change'):
921 self.log.debug(" Changeish does not support dependencies")
922 return True
James E. Blaire421a232012-07-25 16:59:21 -0700923 if not change.needs_change:
924 self.log.debug(" No changes needed")
925 return True
926 if change.needs_change.is_merged:
927 self.log.debug(" Needed change is merged")
928 return True
929 if not change.needs_change.is_current_patchset:
930 self.log.debug(" Needed change is not the current patchset")
931 return False
James E. Blair127bc182012-08-28 15:55:15 -0700932 if self.isChangeAlreadyInQueue(change.needs_change):
James E. Blaire421a232012-07-25 16:59:21 -0700933 self.log.debug(" Needed change is already ahead in the queue")
934 return True
James E. Blaire0487072012-08-29 17:38:31 -0700935 if self.sched.trigger.canMerge(change.needs_change,
936 self.getSubmitAllowNeeds()):
937 self.log.debug(" Change %s is needed" %
938 change.needs_change)
939 return change.needs_change
James E. Blaire421a232012-07-25 16:59:21 -0700940 # The needed change can't be merged.
941 self.log.debug(" Change %s is needed but can not be merged" %
942 change.needs_change)
943 return False
944
James E. Blairee743612012-05-29 14:49:32 -0700945 def _getDependentChanges(self, change):
James E. Blair9f9667e2012-06-12 17:51:08 -0700946 orig_change = change
James E. Blairee743612012-05-29 14:49:32 -0700947 changes = []
948 while change.change_ahead:
949 changes.append(change.change_ahead)
950 change = change.change_ahead
James E. Blair9f9667e2012-06-12 17:51:08 -0700951 self.log.info("Change %s depends on changes %s" % (orig_change,
952 changes))
James E. Blairee743612012-05-29 14:49:32 -0700953 return changes
954
James E. Blairdaabed22012-08-15 15:38:57 -0700955 def _launchJobs(self, change, jobs):
James E. Blairee743612012-05-29 14:49:32 -0700956 self.log.debug("Launching jobs for change %s" % change)
James E. Blair4886cc12012-07-18 15:39:41 -0700957 ref = change.current_build_set.getRef()
James E. Blairdaabed22012-08-15 15:38:57 -0700958 if hasattr(change, 'refspec') and not ref:
James E. Blair4886cc12012-07-18 15:39:41 -0700959 change.current_build_set.setConfiguration()
960 ref = change.current_build_set.getRef()
961 dependent_changes = self._getDependentChanges(change)
962 dependent_changes.reverse()
James E. Blair973721f2012-08-15 10:19:43 -0700963 all_changes = dependent_changes + [change]
964 merged = self.sched.merger.mergeChanges(all_changes, ref)
965 if not merged:
966 self.log.info("Unable to merge changes %s" % all_changes)
967 self.pipeline.setUnableToMerge(change)
968 self.possiblyReportChange(change)
969 return
James E. Blair4886cc12012-07-18 15:39:41 -0700970 #TODO: remove this line after GERRIT_CHANGES is gone
James E. Blairee743612012-05-29 14:49:32 -0700971 dependent_changes = self._getDependentChanges(change)
James E. Blairdaabed22012-08-15 15:38:57 -0700972 for job in jobs:
James E. Blairee743612012-05-29 14:49:32 -0700973 self.log.debug("Found job %s for change %s" % (job, change))
974 try:
James E. Blair4886cc12012-07-18 15:39:41 -0700975 #TODO: remove dependent_changes after GERRIT_CHANGES is gone
James E. Blair03b94ef2012-08-20 10:54:29 -0700976 build = self.sched.launcher.launch(job, change, self.pipeline,
James E. Blairee743612012-05-29 14:49:32 -0700977 dependent_changes)
978 self.building_jobs[build] = change
Zhongyue Luo1c860d72012-07-19 11:03:56 +0800979 self.log.debug("Adding build %s of job %s to change %s" %
980 (build, job, change))
James E. Blairee743612012-05-29 14:49:32 -0700981 change.addBuild(build)
982 except:
Zhongyue Luo1c860d72012-07-19 11:03:56 +0800983 self.log.exception("Exception while launching job %s "
984 "for change %s:" % (job, change))
James E. Blairdaabed22012-08-15 15:38:57 -0700985
Clark Boylan826ef9e2012-07-12 16:44:46 -0700986 def cancelJobs(self, change, prime=True):
James E. Blairee743612012-05-29 14:49:32 -0700987 self.log.debug("Cancel jobs for change %s" % change)
988 to_remove = []
Clark Boylan826ef9e2012-07-12 16:44:46 -0700989 if prime:
990 change.resetAllBuilds()
James E. Blairee743612012-05-29 14:49:32 -0700991 for build, build_change in self.building_jobs.items():
992 if build_change == change:
Zhongyue Luo1c860d72012-07-19 11:03:56 +0800993 self.log.debug("Found build %s for change %s to cancel" %
994 (build, change))
James E. Blairee743612012-05-29 14:49:32 -0700995 try:
996 self.sched.launcher.cancel(build)
997 except:
Zhongyue Luo1c860d72012-07-19 11:03:56 +0800998 self.log.exception("Exception while canceling build %s "
999 "for change %s" % (build, change))
James E. Blairee743612012-05-29 14:49:32 -07001000 to_remove.append(build)
1001 for build in to_remove:
1002 self.log.debug("Removing build %s from running builds" % build)
James E. Blair11700c32012-07-05 17:50:05 -07001003 build.result = 'CANCELED'
James E. Blairee743612012-05-29 14:49:32 -07001004 del self.building_jobs[build]
1005 if change.change_behind:
Zhongyue Luo1c860d72012-07-19 11:03:56 +08001006 self.log.debug("Canceling jobs for change %s, behind change %s" %
1007 (change.change_behind, change))
Clark Boylan826ef9e2012-07-12 16:44:46 -07001008 self.cancelJobs(change.change_behind, prime=prime)
1009
James E. Blaire0487072012-08-29 17:38:31 -07001010 def handleFailedChange(self, change):
1011 # A build failed. All changes behind this change will need to
1012 # be retested. To free up resources cancel the builds behind
1013 # this one as they will be rerun anyways.
1014 change_ahead = change.change_ahead
1015 change_behind = change.change_behind
1016 if not change_ahead:
1017 # If we're at the head of the queue, allow changes to relaunch
1018 if change_behind:
James E. Blairec590122012-08-22 15:19:31 -07001019 self.log.info("Canceling/relaunching jobs for change %s "
1020 "behind failed change %s" %
1021 (change_behind, change))
1022 self.cancelJobs(change_behind)
James E. Blaire0487072012-08-29 17:38:31 -07001023 self.dequeueChange(change)
Clark Boylanafd18ac2012-08-22 12:59:32 -07001024 elif change_behind:
James E. Blaire0487072012-08-29 17:38:31 -07001025 self.log.debug("Canceling builds behind change: %s due to "
1026 "failure." % change)
1027 self.cancelJobs(change_behind, prime=False)
James E. Blair268d9342012-06-13 18:24:29 -07001028
James E. Blaire0487072012-08-29 17:38:31 -07001029 def dequeueChange(self, change):
1030 self.log.debug("Removing change %s from queue" % change)
1031 change_ahead = change.change_ahead
1032 change_behind = change.change_behind
1033 change_queue = self.pipeline.getQueue(change.project)
1034 change_queue.dequeueChange(change)
1035 if not change_ahead and not change.reported:
1036 self.log.debug("Adding %s as a severed head" % change)
1037 change_queue.addSeveredHead(change)
1038 self.dequeueDependentChanges(change_behind)
1039
1040 def dequeueDependentChanges(self, change):
James E. Blaircaec0c52012-08-22 14:52:22 -07001041 # When a change is dequeued after failing, dequeue any changes that
1042 # depend on it.
James E. Blaircaec0c52012-08-22 14:52:22 -07001043 while change:
1044 change_behind = change.change_behind
James E. Blaire0487072012-08-29 17:38:31 -07001045 if self.checkForChangesNeededBy(change) is not True:
James E. Blaircaec0c52012-08-22 14:52:22 -07001046 # It's not okay to enqueue this change, we should remove it.
James E. Blaircaec0c52012-08-22 14:52:22 -07001047 self.log.info("Dequeuing change %s because "
1048 "it can no longer merge" % change)
James E. Blaire0487072012-08-29 17:38:31 -07001049 change_queue = self.pipeline.getQueue(change.project)
1050 change_queue.dequeueChange(change)
James E. Blaircaec0c52012-08-22 14:52:22 -07001051 self.pipeline.setDequeuedNeedingChange(change)
1052 self.reportChange(change)
1053 # We don't need to recurse, because any changes that might
1054 # be affected by the removal of this change are behind us
1055 # in the queue, so we can continue walking backwards.
1056 change = change_behind