blob: 33bac35c6ecee762cb13df4ceaa6e3266900e17b [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,
Zhongyue Luoaa85ebf2012-09-21 16:38:33 +0800101 comment_filters=
102 toList(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
Clark Boylan9b670902012-09-28 13:47:56 -0700481 if self.sched.config.has_option('zuul', 'status_url'):
482 msg += "\n" + self.sched.config.get('zuul', 'status_url')
James E. Blaire0487072012-08-29 17:38:31 -0700483 ret = self.sched.trigger.report(change, msg, self.start_action)
484 if ret:
485 self.log.error("Reporting change start %s received: %s" %
486 (change, ret))
487 except:
488 self.log.exception("Exception while reporting start:")
489
490 def isChangeReadyToBeEnqueued(self, change):
491 return True
492
493 def enqueueChangesAhead(self, change):
494 return True
495
496 def enqueueChangesBehind(self, change):
497 return True
498
James E. Blairee743612012-05-29 14:49:32 -0700499 def addChange(self, change):
James E. Blaire0487072012-08-29 17:38:31 -0700500 self.log.debug("Considering adding change %s" % change)
James E. Blair0dc8ba92012-07-16 14:23:52 -0700501 if self.isChangeAlreadyInQueue(change):
502 self.log.debug("Change %s is already in queue, ignoring" % change)
James E. Blaire0487072012-08-29 17:38:31 -0700503 return True
James E. Blair692c6b32012-07-17 11:16:35 -0700504
James E. Blaire0487072012-08-29 17:38:31 -0700505 if not self.isChangeReadyToBeEnqueued(change):
506 self.log.debug("Change %s is not ready to be enqueued, ignoring" %
507 change)
508 return False
509
510 if not self.enqueueChangesAhead(change):
511 self.log.debug("Failed to enqueue changes ahead of" % change)
512 return False
513
514 if self.isChangeAlreadyInQueue(change):
515 self.log.debug("Change %s is already in queue, ignoring" % change)
516 return True
517
518 change_queue = self.pipeline.getQueue(change.project)
519 if change_queue:
520 self.log.debug("Adding change %s to queue %s" %
521 (change, change_queue))
522 if self.start_action:
523 self.reportStart(change)
524 change_queue.enqueueChange(change)
525 self.enqueueChangesBehind(change)
526 else:
527 self.log.error("Unable to find change queue for project %s" %
528 change.project)
529 return False
530 self.launchJobs()
James E. Blairee743612012-05-29 14:49:32 -0700531
James E. Blairdaabed22012-08-15 15:38:57 -0700532 def _launchJobs(self, change, jobs):
James E. Blairee743612012-05-29 14:49:32 -0700533 self.log.debug("Launching jobs for change %s" % change)
James E. Blair4886cc12012-07-18 15:39:41 -0700534 ref = change.current_build_set.getRef()
James E. Blairdaabed22012-08-15 15:38:57 -0700535 if hasattr(change, 'refspec') and not ref:
James E. Blair4886cc12012-07-18 15:39:41 -0700536 change.current_build_set.setConfiguration()
537 ref = change.current_build_set.getRef()
Zhongyue Luoaa85ebf2012-09-21 16:38:33 +0800538 mode = model.MERGE_IF_NECESSARY
539 merged = self.sched.merger.mergeChanges([change], ref, mode=mode)
James E. Blair973721f2012-08-15 10:19:43 -0700540 if not merged:
541 self.log.info("Unable to merge change %s" % change)
542 self.pipeline.setUnableToMerge(change)
543 self.possiblyReportChange(change)
544 return
James E. Blair4886cc12012-07-18 15:39:41 -0700545
James E. Blair4aea70c2012-07-26 14:23:24 -0700546 for job in self.pipeline.findJobsToRun(change):
James E. Blairee743612012-05-29 14:49:32 -0700547 self.log.debug("Found job %s for change %s" % (job, change))
548 try:
James E. Blair03b94ef2012-08-20 10:54:29 -0700549 build = self.sched.launcher.launch(job, change, self.pipeline)
James E. Blairee743612012-05-29 14:49:32 -0700550 self.building_jobs[build] = change
Zhongyue Luo1c860d72012-07-19 11:03:56 +0800551 self.log.debug("Adding build %s of job %s to change %s" %
552 (build, job, change))
James E. Blairee743612012-05-29 14:49:32 -0700553 change.addBuild(build)
554 except:
Zhongyue Luo1c860d72012-07-19 11:03:56 +0800555 self.log.exception("Exception while launching job %s "
556 "for change %s:" % (job, change))
James E. Blairee743612012-05-29 14:49:32 -0700557
James E. Blaire0487072012-08-29 17:38:31 -0700558 def launchJobs(self, change=None):
559 if not change:
560 for change in self.pipeline.getAllChanges():
561 self.launchJobs(change)
562 return
James E. Blairdaabed22012-08-15 15:38:57 -0700563 jobs = self.pipeline.findJobsToRun(change)
564 if jobs:
565 self._launchJobs(change, jobs)
566
James E. Blair11700c32012-07-05 17:50:05 -0700567 def updateBuildDescriptions(self, build_set):
568 for build in build_set.getBuilds():
James E. Blair8b0d4c42012-08-23 16:03:05 -0700569 desc = self.formatDescription(build)
James E. Blair11700c32012-07-05 17:50:05 -0700570 self.sched.launcher.setBuildDescription(build, desc)
571
572 if build_set.previous_build_set:
573 for build in build_set.previous_build_set.getBuilds():
James E. Blair8b0d4c42012-08-23 16:03:05 -0700574 desc = self.formatDescription(build)
James E. Blair11700c32012-07-05 17:50:05 -0700575 self.sched.launcher.setBuildDescription(build, desc)
576
577 def onBuildStarted(self, build):
578 self.log.debug("Build %s started" % build)
579 if build not in self.building_jobs:
580 self.log.debug("Build %s not found" % (build))
581 # Or triggered externally, or triggered before zuul started,
582 # or restarted
583 return False
584
585 self.updateBuildDescriptions(build.build_set)
586 return True
587
James E. Blaire0487072012-08-29 17:38:31 -0700588 def handleFailedChange(self, change):
589 pass
590
James E. Blairee743612012-05-29 14:49:32 -0700591 def onBuildCompleted(self, build):
592 self.log.debug("Build %s completed" % build)
James E. Blair1e8dd892012-05-30 09:15:05 -0700593 if build not in self.building_jobs:
James E. Blairc84dd262012-05-31 10:03:13 -0700594 self.log.debug("Build %s not found" % (build))
James E. Blairee743612012-05-29 14:49:32 -0700595 # Or triggered externally, or triggered before zuul started,
596 # or restarted
597 return False
598 change = self.building_jobs[build]
Zhongyue Luo1c860d72012-07-19 11:03:56 +0800599 self.log.debug("Found change %s which triggered completed build %s" %
600 (change, build))
James E. Blairee743612012-05-29 14:49:32 -0700601
602 del self.building_jobs[build]
603
James E. Blair4aea70c2012-07-26 14:23:24 -0700604 self.pipeline.setResult(change, build)
Zhongyue Luo1c860d72012-07-19 11:03:56 +0800605 self.log.info("Change %s status is now:\n %s" %
James E. Blaire0487072012-08-29 17:38:31 -0700606 (change, self.pipeline.formatStatus(change)))
James E. Blairee743612012-05-29 14:49:32 -0700607
James E. Blaire0487072012-08-29 17:38:31 -0700608 if self.pipeline.didAnyJobFail(change):
609 self.handleFailedChange(change)
James E. Blair11700c32012-07-05 17:50:05 -0700610
James E. Blaire0487072012-08-29 17:38:31 -0700611 self.reportChanges()
612 self.launchJobs()
James E. Blair11700c32012-07-05 17:50:05 -0700613 self.updateBuildDescriptions(build.build_set)
James E. Blairee743612012-05-29 14:49:32 -0700614 return True
615
James E. Blaire0487072012-08-29 17:38:31 -0700616 def reportChanges(self):
617 self.log.debug("Searching for changes to report")
618 reported = False
619 for change in self.pipeline.getAllChanges():
620 self.log.debug(" checking %s" % change)
621 if self.pipeline.areAllJobsComplete(change):
622 self.log.debug(" possibly reporting %s" % change)
623 if self.possiblyReportChange(change):
624 reported = True
625 if reported:
626 self.reportChanges()
627 self.log.debug("Done searching for changes to report")
628
James E. Blairee743612012-05-29 14:49:32 -0700629 def possiblyReportChange(self, change):
630 self.log.debug("Possibly reporting change %s" % change)
James E. Blaire0487072012-08-29 17:38:31 -0700631 # Even if a change isn't reportable, keep going so that it
632 # gets dequeued in the normal manner.
633 if change.is_reportable and change.reported:
634 self.log.debug("Change %s already reported" % change)
635 return False
636 change_ahead = change.change_ahead
637 if not change_ahead:
638 self.log.debug("Change %s is at the front of the queue, "
639 "reporting" % (change))
640 ret = self.reportChange(change)
641 if self.changes_merge:
642 succeeded = self.pipeline.didAllJobsSucceed(change)
643 merged = (not ret)
644 if merged:
645 merged = self.sched.trigger.isMerged(change, change.branch)
646 self.log.info("Reported change %s status: all-succeeded: %s, "
647 "merged: %s" % (change, succeeded, merged))
648 if not (succeeded and merged):
649 self.log.debug("Reported change %s failed tests or failed "
650 "to merge" % (change))
651 self.handleFailedChange(change)
652 return True
653 self.log.debug("Removing reported change %s from queue" %
654 change)
655 change_queue = self.pipeline.getQueue(change.project)
656 change_queue.dequeueChange(change)
657 return True
James E. Blairee743612012-05-29 14:49:32 -0700658
659 def reportChange(self, change):
James E. Blair4aea70c2012-07-26 14:23:24 -0700660 if not change.is_reportable:
661 return False
James E. Blair6173fb32012-07-11 17:23:33 -0700662 if change.reported:
James E. Blairb0fcae42012-07-17 11:12:10 -0700663 return 0
James E. Blair4aea70c2012-07-26 14:23:24 -0700664 self.log.debug("Reporting change %s" % change)
James E. Blairee743612012-05-29 14:49:32 -0700665 ret = None
James E. Blair4aea70c2012-07-26 14:23:24 -0700666 if self.pipeline.didAllJobsSucceed(change):
James E. Blairee743612012-05-29 14:49:32 -0700667 action = self.success_action
James E. Blair11700c32012-07-05 17:50:05 -0700668 change.setReportedResult('SUCCESS')
James E. Blairee743612012-05-29 14:49:32 -0700669 else:
670 action = self.failure_action
James E. Blair11700c32012-07-05 17:50:05 -0700671 change.setReportedResult('FAILURE')
James E. Blair8b0d4c42012-08-23 16:03:05 -0700672 report = self.formatReport(change)
James E. Blaire0487072012-08-29 17:38:31 -0700673 change.reported = True
James E. Blairee743612012-05-29 14:49:32 -0700674 try:
Zhongyue Luo1c860d72012-07-19 11:03:56 +0800675 self.log.info("Reporting change %s, action: %s" %
676 (change, action))
James E. Blair8b0d4c42012-08-23 16:03:05 -0700677 ret = self.sched.trigger.report(change, report, action)
James E. Blairee743612012-05-29 14:49:32 -0700678 if ret:
Zhongyue Luo1c860d72012-07-19 11:03:56 +0800679 self.log.error("Reporting change %s received: %s" %
680 (change, ret))
James E. Blairee743612012-05-29 14:49:32 -0700681 except:
682 self.log.exception("Exception while reporting:")
James E. Blair11700c32012-07-05 17:50:05 -0700683 change.setReportedResult('ERROR')
684 self.updateBuildDescriptions(change.current_build_set)
James E. Blairee743612012-05-29 14:49:32 -0700685 return ret
686
James E. Blair8b0d4c42012-08-23 16:03:05 -0700687 def formatReport(self, changeish):
688 ret = ''
689 if self.pipeline.didAllJobsSucceed(changeish):
690 ret += 'Build successful\n\n'
691 else:
692 ret += 'Build failed\n\n'
693
694 if changeish.dequeued_needing_change:
695 ret += "This change depends on a change that failed to merge."
696 elif changeish.current_build_set.unable_to_merge:
697 ret += "This change was unable to be automatically merged "\
698 "with the current state of the repository. Please "\
699 "rebase your change and upload a new patchset."
700 else:
James E. Blaira35fcce2012-08-24 10:46:01 -0700701 if self.sched.config.has_option('zuul', 'url_pattern'):
702 pattern = self.sched.config.get('zuul', 'url_pattern')
703 else:
704 pattern = None
James E. Blair8b0d4c42012-08-23 16:03:05 -0700705 for job in self.pipeline.getJobs(changeish):
706 build = changeish.current_build_set.getBuild(job.name)
707 result = build.result
708 if result == 'SUCCESS' and job.success_message:
709 result = job.success_message
710 elif result == 'FAILURE' and job.failure_message:
711 result = job.failure_message
James E. Blair7ee88a22012-09-12 18:59:31 +0200712 url = None
James E. Blair8b0d4c42012-08-23 16:03:05 -0700713 if build.url:
714 if pattern:
715 url = pattern.format(change=changeish,
716 pipeline=self.pipeline,
717 job=job,
718 build=build)
719 else:
720 url = build.url
721 if not url:
722 url = job.name
723 if not job.voting:
724 voting = ' (non-voting)'
725 else:
726 voting = ''
727 ret += '- %s : %s%s\n' % (url, result, voting)
728 return ret
729
730 def formatDescription(self, build):
731 concurrent_changes = ''
732 concurrent_builds = ''
733 other_builds = ''
734
735 for change in build.build_set.other_changes:
736 concurrent_changes += '<li><a href="{change.url}">\
737 {change.number},{change.patchset}</a></li>'.format(
738 change=change)
739
740 change = build.build_set.change
741
742 for build in build.build_set.getBuilds():
743 if build.base_url:
744 concurrent_builds += """\
745<li>
746 <a href="{build.base_url}">
747 {build.job.name} #{build.number}</a>: {build.result}
748</li>
749""".format(build=build)
750 else:
751 concurrent_builds += """\
752<li>
753 {build.job.name}: {build.result}
754</li>""".format(build=build)
755
756 if build.build_set.previous_build_set:
757 other_build = build.build_set.previous_build_set.getBuild(
758 build.job.name)
759 if other_build:
760 other_builds += """\
761<li>
762 Preceded by: <a href="{build.base_url}">
763 {build.job.name} #{build.number}</a>
764</li>
765""".format(build=other_build)
766
767 if build.build_set.next_build_set:
768 other_build = build.build_set.next_build_set.getBuild(
769 build.job.name)
770 if other_build:
771 other_builds += """\
772<li>
773 Succeeded by: <a href="{build.base_url}">
774 {build.job.name} #{build.number}</a>
775</li>
776""".format(build=other_build)
777
778 result = build.build_set.result
779
780 if hasattr(change, 'number'):
781 ret = """\
782<p>
783 Triggered by change:
784 <a href="{change.url}">{change.number},{change.patchset}</a><br/>
785 Branch: <b>{change.branch}</b><br/>
786 Pipeline: <b>{self.pipeline.name}</b>
787</p>"""
788 else:
789 ret = """\
790<p>
791 Triggered by reference:
792 {change.ref}</a><br/>
793 Old revision: <b>{change.oldrev}</b><br/>
794 New revision: <b>{change.newrev}</b><br/>
795 Pipeline: <b>{self.pipeline.name}</b>
796</p>"""
797
798 if concurrent_changes:
799 ret += """\
800<p>
801 Other changes tested concurrently with this change:
802 <ul>{concurrent_changes}</ul>
803</p>
804"""
805 if concurrent_builds:
806 ret += """\
807<p>
808 All builds for this change set:
809 <ul>{concurrent_builds}</ul>
810</p>
811"""
812
813 if other_builds:
814 ret += """\
815<p>
816 Other build sets for this change:
817 <ul>{other_builds}</ul>
818</p>
819"""
820 if result:
821 ret += """\
822<p>
823 Reported result: <b>{result}</b>
824</p>
825"""
826
827 ret = ret.format(**locals())
James E. Blair268d9342012-06-13 18:24:29 -0700828 return ret
829
James E. Blair1e8dd892012-05-30 09:15:05 -0700830
James E. Blair4aea70c2012-07-26 14:23:24 -0700831class IndependentPipelineManager(BasePipelineManager):
832 log = logging.getLogger("zuul.IndependentPipelineManager")
James E. Blaire0487072012-08-29 17:38:31 -0700833 changes_merge = False
834
835 def _postConfig(self):
836 super(IndependentPipelineManager, self)._postConfig()
837
838 change_queue = ChangeQueue(self.pipeline, dependent=False)
839 for project in self.pipeline.getProjects():
840 change_queue.addProject(project)
841
842 self.pipeline.addQueue(change_queue)
James E. Blairee743612012-05-29 14:49:32 -0700843
James E. Blair1e8dd892012-05-30 09:15:05 -0700844
James E. Blair4aea70c2012-07-26 14:23:24 -0700845class DependentPipelineManager(BasePipelineManager):
846 log = logging.getLogger("zuul.DependentPipelineManager")
James E. Blaire0487072012-08-29 17:38:31 -0700847 changes_merge = True
James E. Blairee743612012-05-29 14:49:32 -0700848
849 def __init__(self, *args, **kwargs):
James E. Blair4aea70c2012-07-26 14:23:24 -0700850 super(DependentPipelineManager, self).__init__(*args, **kwargs)
James E. Blairee743612012-05-29 14:49:32 -0700851
852 def _postConfig(self):
James E. Blair4aea70c2012-07-26 14:23:24 -0700853 super(DependentPipelineManager, self)._postConfig()
James E. Blairee743612012-05-29 14:49:32 -0700854 self.buildChangeQueues()
855
856 def buildChangeQueues(self):
857 self.log.debug("Building shared change queues")
858 change_queues = []
859
James E. Blair4aea70c2012-07-26 14:23:24 -0700860 for project in self.pipeline.getProjects():
861 change_queue = ChangeQueue(self.pipeline)
862 change_queue.addProject(project)
863 change_queues.append(change_queue)
864 self.log.debug("Created queue: %s" % change_queue)
James E. Blairee743612012-05-29 14:49:32 -0700865
866 self.log.debug("Combining shared queues")
867 new_change_queues = []
868 for a in change_queues:
869 merged_a = False
870 for b in new_change_queues:
871 if not a.getJobs().isdisjoint(b.getJobs()):
872 self.log.debug("Merging queue %s into %s" % (a, b))
873 b.mergeChangeQueue(a)
874 merged_a = True
875 break # this breaks out of 'for b' and continues 'for a'
876 if not merged_a:
877 self.log.debug("Keeping queue %s" % (a))
878 new_change_queues.append(a)
James E. Blair1e8dd892012-05-30 09:15:05 -0700879
James E. Blairee743612012-05-29 14:49:32 -0700880 self.log.info(" Shared change queues:")
James E. Blaire0487072012-08-29 17:38:31 -0700881 for queue in new_change_queues:
882 self.pipeline.addQueue(queue)
883 self.log.info(" %s" % queue)
James E. Blairee743612012-05-29 14:49:32 -0700884
James E. Blaire0487072012-08-29 17:38:31 -0700885 def isChangeReadyToBeEnqueued(self, change):
886 if not self.sched.trigger.canMerge(change,
887 self.getSubmitAllowNeeds()):
888 self.log.debug("Change %s can not merge, ignoring" % change)
889 return False
890 return True
James E. Blair1e8dd892012-05-30 09:15:05 -0700891
James E. Blaire0487072012-08-29 17:38:31 -0700892 def enqueueChangesBehind(self, change):
893 to_enqueue = []
894 self.log.debug("Checking for changes needing %s:" % change)
895 if not hasattr(change, 'needed_by_changes'):
896 self.log.debug(" Changeish does not support dependencies")
897 return
898 for needs in change.needed_by_changes:
899 if self.sched.trigger.canMerge(needs,
900 self.getSubmitAllowNeeds()):
901 self.log.debug(" Change %s needs %s and is ready to merge" %
902 (needs, change))
903 to_enqueue.append(needs)
904 if not to_enqueue:
905 self.log.debug(" No changes need %s" % change)
906
907 for other_change in to_enqueue:
908 self.addChange(other_change)
909
910 def enqueueChangesAhead(self, change):
911 ret = self.checkForChangesNeededBy(change)
912 if ret in [True, False]:
913 return ret
914 self.log.debug(" Change %s must be merged ahead of %s" %
915 (ret, change))
916 return self.addChange(ret)
917
918 def checkForChangesNeededBy(self, change):
James E. Blaire421a232012-07-25 16:59:21 -0700919 self.log.debug("Checking for changes needed by %s:" % change)
920 # Return true if okay to proceed enqueing this change,
921 # false if the change should not be enqueued.
James E. Blair4aea70c2012-07-26 14:23:24 -0700922 if not hasattr(change, 'needs_change'):
923 self.log.debug(" Changeish does not support dependencies")
924 return True
James E. Blaire421a232012-07-25 16:59:21 -0700925 if not change.needs_change:
926 self.log.debug(" No changes needed")
927 return True
928 if change.needs_change.is_merged:
929 self.log.debug(" Needed change is merged")
930 return True
931 if not change.needs_change.is_current_patchset:
932 self.log.debug(" Needed change is not the current patchset")
933 return False
James E. Blair127bc182012-08-28 15:55:15 -0700934 if self.isChangeAlreadyInQueue(change.needs_change):
James E. Blaire421a232012-07-25 16:59:21 -0700935 self.log.debug(" Needed change is already ahead in the queue")
936 return True
James E. Blaire0487072012-08-29 17:38:31 -0700937 if self.sched.trigger.canMerge(change.needs_change,
938 self.getSubmitAllowNeeds()):
939 self.log.debug(" Change %s is needed" %
940 change.needs_change)
941 return change.needs_change
James E. Blaire421a232012-07-25 16:59:21 -0700942 # The needed change can't be merged.
943 self.log.debug(" Change %s is needed but can not be merged" %
944 change.needs_change)
945 return False
946
James E. Blairee743612012-05-29 14:49:32 -0700947 def _getDependentChanges(self, change):
James E. Blair9f9667e2012-06-12 17:51:08 -0700948 orig_change = change
James E. Blairee743612012-05-29 14:49:32 -0700949 changes = []
950 while change.change_ahead:
951 changes.append(change.change_ahead)
952 change = change.change_ahead
James E. Blair9f9667e2012-06-12 17:51:08 -0700953 self.log.info("Change %s depends on changes %s" % (orig_change,
954 changes))
James E. Blairee743612012-05-29 14:49:32 -0700955 return changes
956
James E. Blairdaabed22012-08-15 15:38:57 -0700957 def _launchJobs(self, change, jobs):
James E. Blairee743612012-05-29 14:49:32 -0700958 self.log.debug("Launching jobs for change %s" % change)
James E. Blair4886cc12012-07-18 15:39:41 -0700959 ref = change.current_build_set.getRef()
James E. Blairdaabed22012-08-15 15:38:57 -0700960 if hasattr(change, 'refspec') and not ref:
James E. Blair4886cc12012-07-18 15:39:41 -0700961 change.current_build_set.setConfiguration()
962 ref = change.current_build_set.getRef()
963 dependent_changes = self._getDependentChanges(change)
964 dependent_changes.reverse()
James E. Blair973721f2012-08-15 10:19:43 -0700965 all_changes = dependent_changes + [change]
966 merged = self.sched.merger.mergeChanges(all_changes, ref)
967 if not merged:
968 self.log.info("Unable to merge changes %s" % all_changes)
969 self.pipeline.setUnableToMerge(change)
970 self.possiblyReportChange(change)
971 return
James E. Blair4886cc12012-07-18 15:39:41 -0700972 #TODO: remove this line after GERRIT_CHANGES is gone
James E. Blairee743612012-05-29 14:49:32 -0700973 dependent_changes = self._getDependentChanges(change)
James E. Blairdaabed22012-08-15 15:38:57 -0700974 for job in jobs:
James E. Blairee743612012-05-29 14:49:32 -0700975 self.log.debug("Found job %s for change %s" % (job, change))
976 try:
James E. Blair4886cc12012-07-18 15:39:41 -0700977 #TODO: remove dependent_changes after GERRIT_CHANGES is gone
James E. Blair03b94ef2012-08-20 10:54:29 -0700978 build = self.sched.launcher.launch(job, change, self.pipeline,
James E. Blairee743612012-05-29 14:49:32 -0700979 dependent_changes)
980 self.building_jobs[build] = change
Zhongyue Luo1c860d72012-07-19 11:03:56 +0800981 self.log.debug("Adding build %s of job %s to change %s" %
982 (build, job, change))
James E. Blairee743612012-05-29 14:49:32 -0700983 change.addBuild(build)
984 except:
Zhongyue Luo1c860d72012-07-19 11:03:56 +0800985 self.log.exception("Exception while launching job %s "
986 "for change %s:" % (job, change))
James E. Blairdaabed22012-08-15 15:38:57 -0700987
Clark Boylan826ef9e2012-07-12 16:44:46 -0700988 def cancelJobs(self, change, prime=True):
James E. Blairee743612012-05-29 14:49:32 -0700989 self.log.debug("Cancel jobs for change %s" % change)
990 to_remove = []
Clark Boylan826ef9e2012-07-12 16:44:46 -0700991 if prime:
992 change.resetAllBuilds()
James E. Blairee743612012-05-29 14:49:32 -0700993 for build, build_change in self.building_jobs.items():
994 if build_change == change:
Zhongyue Luo1c860d72012-07-19 11:03:56 +0800995 self.log.debug("Found build %s for change %s to cancel" %
996 (build, change))
James E. Blairee743612012-05-29 14:49:32 -0700997 try:
998 self.sched.launcher.cancel(build)
999 except:
Zhongyue Luo1c860d72012-07-19 11:03:56 +08001000 self.log.exception("Exception while canceling build %s "
1001 "for change %s" % (build, change))
James E. Blairee743612012-05-29 14:49:32 -07001002 to_remove.append(build)
1003 for build in to_remove:
1004 self.log.debug("Removing build %s from running builds" % build)
James E. Blair11700c32012-07-05 17:50:05 -07001005 build.result = 'CANCELED'
James E. Blairee743612012-05-29 14:49:32 -07001006 del self.building_jobs[build]
1007 if change.change_behind:
Zhongyue Luo1c860d72012-07-19 11:03:56 +08001008 self.log.debug("Canceling jobs for change %s, behind change %s" %
1009 (change.change_behind, change))
Clark Boylan826ef9e2012-07-12 16:44:46 -07001010 self.cancelJobs(change.change_behind, prime=prime)
1011
James E. Blaire0487072012-08-29 17:38:31 -07001012 def handleFailedChange(self, change):
1013 # A build failed. All changes behind this change will need to
1014 # be retested. To free up resources cancel the builds behind
1015 # this one as they will be rerun anyways.
1016 change_ahead = change.change_ahead
1017 change_behind = change.change_behind
1018 if not change_ahead:
1019 # If we're at the head of the queue, allow changes to relaunch
1020 if change_behind:
James E. Blairec590122012-08-22 15:19:31 -07001021 self.log.info("Canceling/relaunching jobs for change %s "
1022 "behind failed change %s" %
1023 (change_behind, change))
1024 self.cancelJobs(change_behind)
James E. Blaire0487072012-08-29 17:38:31 -07001025 self.dequeueChange(change)
Clark Boylanafd18ac2012-08-22 12:59:32 -07001026 elif change_behind:
James E. Blaire0487072012-08-29 17:38:31 -07001027 self.log.debug("Canceling builds behind change: %s due to "
1028 "failure." % change)
1029 self.cancelJobs(change_behind, prime=False)
James E. Blair268d9342012-06-13 18:24:29 -07001030
James E. Blaire0487072012-08-29 17:38:31 -07001031 def dequeueChange(self, change):
1032 self.log.debug("Removing change %s from queue" % change)
1033 change_ahead = change.change_ahead
1034 change_behind = change.change_behind
1035 change_queue = self.pipeline.getQueue(change.project)
1036 change_queue.dequeueChange(change)
1037 if not change_ahead and not change.reported:
1038 self.log.debug("Adding %s as a severed head" % change)
1039 change_queue.addSeveredHead(change)
1040 self.dequeueDependentChanges(change_behind)
1041
1042 def dequeueDependentChanges(self, change):
James E. Blaircaec0c52012-08-22 14:52:22 -07001043 # When a change is dequeued after failing, dequeue any changes that
1044 # depend on it.
James E. Blaircaec0c52012-08-22 14:52:22 -07001045 while change:
1046 change_behind = change.change_behind
James E. Blaire0487072012-08-29 17:38:31 -07001047 if self.checkForChangesNeededBy(change) is not True:
James E. Blaircaec0c52012-08-22 14:52:22 -07001048 # It's not okay to enqueue this change, we should remove it.
James E. Blaircaec0c52012-08-22 14:52:22 -07001049 self.log.info("Dequeuing change %s because "
1050 "it can no longer merge" % change)
James E. Blaire0487072012-08-29 17:38:31 -07001051 change_queue = self.pipeline.getQueue(change.project)
1052 change_queue.dequeueChange(change)
James E. Blaircaec0c52012-08-22 14:52:22 -07001053 self.pipeline.setDequeuedNeedingChange(change)
1054 self.reportChange(change)
1055 # We don't need to recurse, because any changes that might
1056 # be affected by the removal of this change are behind us
1057 # in the queue, so we can continue walking backwards.
1058 change = change_behind