blob: ab147bae0b30b63fd372b5e2e0a064c4ebaf5f4a [file] [log] [blame]
James E. Blairdbfe1cd2015-02-07 11:41:19 -08001# Copyright 2012-2015 Hewlett-Packard Development Company, L.P.
James E. Blair47958382013-01-10 17:26:02 -08002# Copyright 2013 OpenStack Foundation
Antoine Musso80edd5a2013-02-13 15:37:53 +01003# Copyright 2013 Antoine "hashar" Musso
4# Copyright 2013 Wikimedia Foundation Inc.
James E. Blairee743612012-05-29 14:49:32 -07005#
6# Licensed under the Apache License, Version 2.0 (the "License"); you may
7# not use this file except in compliance with the License. You may obtain
8# a copy of the License at
9#
10# http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15# License for the specific language governing permissions and limitations
16# under the License.
17
James E. Blair71e94122012-12-24 17:53:08 -080018import extras
James E. Blair8dbd56a2012-12-22 10:55:10 -080019import json
James E. Blairee743612012-05-29 14:49:32 -070020import logging
Zhongyue Luo1c860d72012-07-19 11:03:56 +080021import os
James E. Blair5d5bc2b2012-07-06 10:24:01 -070022import pickle
Monty Taylorb934c1a2017-06-16 19:31:47 -050023import queue
James E. Blair8b2a1472017-02-19 15:33:55 -080024import socket
James E. Blair36658cf2013-12-06 17:53:48 -080025import sys
Zhongyue Luo1c860d72012-07-19 11:03:56 +080026import threading
James E. Blair71e94122012-12-24 17:53:08 -080027import time
James E. Blairee743612012-05-29 14:49:32 -070028
Joshua Hesketh0aa7e8b2016-07-14 00:12:25 +100029from zuul import configloader
Morgan Fainberg9c4700a2016-05-30 14:25:19 -070030from zuul import model
James E. Blair83005782015-12-11 14:46:03 -080031from zuul import exceptions
Sergey Lukjanov5ba961b2013-12-27 01:21:04 +040032from zuul import version as zuul_version
Tristan Cacqueray91601d72017-06-15 06:00:12 +000033from zuul.lib.config import get_default
James E. Blairee743612012-05-29 14:49:32 -070034
James E. Blair1e8dd892012-05-30 09:15:05 -070035
James E. Blair468c8512013-12-06 13:27:19 -080036class ManagementEvent(object):
37 """An event that should be processed within the main queue run loop"""
38 def __init__(self):
39 self._wait_event = threading.Event()
Morgan Fainberg1b9bd782016-05-30 14:03:30 -070040 self._exc_info = None
James E. Blair468c8512013-12-06 13:27:19 -080041
Morgan Fainberg1b9bd782016-05-30 14:03:30 -070042 def exception(self, exc_info):
43 self._exc_info = exc_info
James E. Blair36658cf2013-12-06 17:53:48 -080044 self._wait_event.set()
45
46 def done(self):
James E. Blair468c8512013-12-06 13:27:19 -080047 self._wait_event.set()
48
49 def wait(self, timeout=None):
50 self._wait_event.wait(timeout)
Morgan Fainberg1b9bd782016-05-30 14:03:30 -070051 if self._exc_info:
Thomas Bechtold7f68ec42017-06-30 14:24:52 +020052 # sys.exc_info returns (type, value, traceback)
53 type_, exception_instance, traceback = self._exc_info
54 raise exception_instance.with_traceback(traceback)
James E. Blair468c8512013-12-06 13:27:19 -080055 return self._wait_event.is_set()
56
57
58class ReconfigureEvent(ManagementEvent):
59 """Reconfigure the scheduler. The layout will be (re-)loaded from
60 the path specified in the configuration.
61
62 :arg ConfigParser config: the new configuration
63 """
64 def __init__(self, config):
65 super(ReconfigureEvent, self).__init__()
66 self.config = config
67
68
James E. Blair646322f2017-01-27 15:50:34 -080069class TenantReconfigureEvent(ManagementEvent):
70 """Reconfigure the given tenant. The layout will be (re-)loaded from
71 the path specified in the configuration.
72
73 :arg Tenant tenant: the tenant to reconfigure
James E. Blaira615c362017-10-02 17:34:42 -070074 :arg Project project: if supplied, clear the cached configuration
75 from this project first
James E. Blair646322f2017-01-27 15:50:34 -080076 """
James E. Blaira615c362017-10-02 17:34:42 -070077 def __init__(self, tenant, project):
James E. Blair646322f2017-01-27 15:50:34 -080078 super(TenantReconfigureEvent, self).__init__()
79 self.tenant = tenant
James E. Blaira615c362017-10-02 17:34:42 -070080 self.project = project
James E. Blair646322f2017-01-27 15:50:34 -080081
82
James E. Blair36658cf2013-12-06 17:53:48 -080083class PromoteEvent(ManagementEvent):
84 """Promote one or more changes to the head of the queue.
85
Paul Belangerbaca3132016-11-04 12:49:54 -040086 :arg str tenant_name: the name of the tenant
James E. Blair36658cf2013-12-06 17:53:48 -080087 :arg str pipeline_name: the name of the pipeline
88 :arg list change_ids: a list of strings of change ids in the form
89 1234,1
90 """
91
Paul Belangerbaca3132016-11-04 12:49:54 -040092 def __init__(self, tenant_name, pipeline_name, change_ids):
James E. Blair36658cf2013-12-06 17:53:48 -080093 super(PromoteEvent, self).__init__()
Paul Belangerbaca3132016-11-04 12:49:54 -040094 self.tenant_name = tenant_name
James E. Blair36658cf2013-12-06 17:53:48 -080095 self.pipeline_name = pipeline_name
96 self.change_ids = change_ids
97
98
James E. Blaird27a96d2014-07-10 13:25:13 -070099class EnqueueEvent(ManagementEvent):
100 """Enqueue a change into a pipeline
101
102 :arg TriggerEvent trigger_event: a TriggerEvent describing the
103 trigger, pipeline, and change to enqueue
104 """
105
106 def __init__(self, trigger_event):
107 super(EnqueueEvent, self).__init__()
108 self.trigger_event = trigger_event
109
110
James E. Blaira84f0e42014-02-06 07:09:22 -0800111class ResultEvent(object):
112 """An event that needs to modify the pipeline state due to a
113 result from an external system."""
114
115 pass
116
117
118class BuildStartedEvent(ResultEvent):
119 """A build has started.
120
121 :arg Build build: The build which has started.
122 """
123
124 def __init__(self, build):
125 self.build = build
126
127
128class BuildCompletedEvent(ResultEvent):
129 """A build has completed
130
131 :arg Build build: The build which has completed.
132 """
133
134 def __init__(self, build):
135 self.build = build
136
137
James E. Blair4076e2b2014-01-28 12:42:20 -0800138class MergeCompletedEvent(ResultEvent):
139 """A remote merge operation has completed
140
141 :arg BuildSet build_set: The build_set which is ready.
James E. Blair4076e2b2014-01-28 12:42:20 -0800142 :arg bool merged: Whether the merge succeeded (changes with refs).
143 :arg bool updated: Whether the repo was updated (changes without refs).
144 :arg str commit: The SHA of the merged commit (changes with refs).
James E. Blair1960d682017-04-28 15:44:14 -0700145 :arg dict repo_state: The starting repo state before the merge.
James E. Blair4076e2b2014-01-28 12:42:20 -0800146 """
147
Tobias Henkel34ee0882017-07-31 22:26:12 +0200148 def __init__(self, build_set, merged, updated, commit,
James E. Blair1960d682017-04-28 15:44:14 -0700149 files, repo_state):
James E. Blair4076e2b2014-01-28 12:42:20 -0800150 self.build_set = build_set
James E. Blair4076e2b2014-01-28 12:42:20 -0800151 self.merged = merged
152 self.updated = updated
153 self.commit = commit
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700154 self.files = files
James E. Blair1960d682017-04-28 15:44:14 -0700155 self.repo_state = repo_state
James E. Blair4076e2b2014-01-28 12:42:20 -0800156
157
James E. Blair8d692392016-04-08 17:47:58 -0700158class NodesProvisionedEvent(ResultEvent):
159 """Nodes have been provisioned for a build_set
160
161 :arg BuildSet build_set: The build_set which has nodes.
162 :arg list of Node objects nodes: The provisioned nodes
163 """
164
165 def __init__(self, request):
166 self.request = request
David Shrewsbury94e95882017-10-04 15:26:04 -0400167 self.request_id = request.id
James E. Blair8d692392016-04-08 17:47:58 -0700168
169
Maru Newby3fe5f852015-01-13 04:22:14 +0000170def toList(item):
171 if not item:
172 return []
173 if isinstance(item, list):
174 return item
175 return [item]
176
177
James E. Blaire9d45c32012-05-31 09:56:45 -0700178class Scheduler(threading.Thread):
James E. Blaire4de4f42017-01-19 10:35:24 -0800179 """The engine of Zuul.
180
181 The Scheduler is reponsible for recieving events and dispatching
182 them to appropriate components (including pipeline managers,
Paul Belanger174a8272017-03-14 13:20:10 -0400183 mergers and executors).
James E. Blaire4de4f42017-01-19 10:35:24 -0800184
185 It runs a single threaded main loop which processes events
186 received one at a time and takes action as appropriate. Other
187 parts of Zuul may run in their own thread, but synchronization is
188 performed within the scheduler to reduce or eliminate the need for
189 locking in most circumstances.
190
191 The main daemon will have one instance of the Scheduler class
192 running which will persist for the life of the process. The
193 Scheduler instance is supplied to other Zuul components so that
194 they can submit events or otherwise communicate with other
195 components.
196
197 """
198
James E. Blairee743612012-05-29 14:49:32 -0700199 log = logging.getLogger("zuul.Scheduler")
200
James E. Blaire4d229c2016-05-25 15:25:41 -0700201 def __init__(self, config, testonly=False):
James E. Blaire9d45c32012-05-31 09:56:45 -0700202 threading.Thread.__init__(self)
James E. Blair8a6f0c22013-07-01 12:31:34 -0400203 self.daemon = True
James E. Blair8b2a1472017-02-19 15:33:55 -0800204 self.hostname = socket.gethostname()
James E. Blairee743612012-05-29 14:49:32 -0700205 self.wake_event = threading.Event()
James E. Blaircdccd972013-07-01 12:10:22 -0700206 self.layout_lock = threading.Lock()
James E. Blaira84f0e42014-02-06 07:09:22 -0800207 self.run_handler_lock = threading.Lock()
James E. Blair5d5bc2b2012-07-06 10:24:01 -0700208 self._pause = False
James E. Blair5d5bc2b2012-07-06 10:24:01 -0700209 self._exit = False
James E. Blairb0fcae42012-07-17 11:12:10 -0700210 self._stopped = False
Paul Belanger174a8272017-03-14 13:20:10 -0400211 self.executor = None
James E. Blair4076e2b2014-01-28 12:42:20 -0800212 self.merger = None
James E. Blair83005782015-12-11 14:46:03 -0800213 self.connections = None
James E. Blair552b54f2016-07-22 13:55:32 -0700214 self.statsd = extras.try_import('statsd.statsd')
James E. Blair83005782015-12-11 14:46:03 -0800215 # TODO(jeblair): fix this
Joshua Hesketh352264b2015-08-11 23:42:08 +1000216 # Despite triggers being part of the pipeline, there is one trigger set
217 # per scheduler. The pipeline handles the trigger filters but since
218 # the events are handled by the scheduler itself it needs to handle
219 # the loading of the triggers.
220 # self.triggers['connection_name'] = triggerObject
James E. Blair6c358e72013-07-29 17:06:47 -0700221 self.triggers = dict()
Joshua Hesketh352264b2015-08-11 23:42:08 +1000222 self.config = config
James E. Blairee743612012-05-29 14:49:32 -0700223
Monty Taylorb934c1a2017-06-16 19:31:47 -0500224 self.trigger_event_queue = queue.Queue()
225 self.result_event_queue = queue.Queue()
226 self.management_event_queue = queue.Queue()
James E. Blair59fdbac2015-12-07 17:08:06 -0800227 self.abide = model.Abide()
James E. Blairee743612012-05-29 14:49:32 -0700228
James E. Blaire4d229c2016-05-25 15:25:41 -0700229 if not testonly:
230 time_dir = self._get_time_database_dir()
231 self.time_database = model.TimeDataBase(time_dir)
James E. Blairce8a2132016-05-19 15:21:52 -0700232
Jeremy Stanley98b38de2015-06-04 21:20:43 +0000233 self.zuul_version = zuul_version.version_info.release_string()
Sergey Lukjanov5d0438d2013-12-24 03:36:39 +0400234 self.last_reconfigured = None
Jesse Keating71a47ff2017-06-06 11:36:43 -0700235 self.tenant_last_reconfigured = {}
David Shrewsburyffab07a2017-07-24 12:45:07 -0400236 self.autohold_requests = {}
Sergey Lukjanov5ba961b2013-12-27 01:21:04 +0400237
James E. Blairb0fcae42012-07-17 11:12:10 -0700238 def stop(self):
239 self._stopped = True
Joshua Hesketh352264b2015-08-11 23:42:08 +1000240 self.stopConnections()
James E. Blairb0fcae42012-07-17 11:12:10 -0700241 self.wake_event.set()
242
Jan Hruban7083edd2015-08-21 14:00:54 +0200243 def registerConnections(self, connections, webapp, load=True):
Joshua Hesketh9a256752016-04-04 13:38:51 +1000244 # load: whether or not to trigger the onLoad for the connection. This
245 # is useful for not doing a full load during layout validation.
Joshua Hesketh352264b2015-08-11 23:42:08 +1000246 self.connections = connections
Jan Hruban7083edd2015-08-21 14:00:54 +0200247 self.connections.registerWebapp(webapp)
Joshua Hesketh0aa7e8b2016-07-14 00:12:25 +1000248 self.connections.registerScheduler(self, load)
Joshua Hesketh352264b2015-08-11 23:42:08 +1000249
250 def stopConnections(self):
James E. Blair83005782015-12-11 14:46:03 -0800251 self.connections.stop()
James E. Blair14abdf42015-12-09 16:11:53 -0800252
Paul Belanger174a8272017-03-14 13:20:10 -0400253 def setExecutor(self, executor):
254 self.executor = executor
James E. Blairee743612012-05-29 14:49:32 -0700255
James E. Blair4076e2b2014-01-28 12:42:20 -0800256 def setMerger(self, merger):
257 self.merger = merger
258
James E. Blair8d692392016-04-08 17:47:58 -0700259 def setNodepool(self, nodepool):
260 self.nodepool = nodepool
261
James E. Blairdce6cea2016-12-20 16:45:32 -0800262 def setZooKeeper(self, zk):
263 self.zk = zk
264
James E. Blairee743612012-05-29 14:49:32 -0700265 def addEvent(self, event):
James E. Blairee743612012-05-29 14:49:32 -0700266 self.trigger_event_queue.put(event)
267 self.wake_event.set()
268
James E. Blair11700c32012-07-05 17:50:05 -0700269 def onBuildStarted(self, build):
James E. Blair71e94122012-12-24 17:53:08 -0800270 build.start_time = time.time()
James E. Blaira84f0e42014-02-06 07:09:22 -0800271 event = BuildStartedEvent(build)
272 self.result_event_queue.put(event)
James E. Blair11700c32012-07-05 17:50:05 -0700273 self.wake_event.set()
274
James E. Blair196f61a2017-06-30 15:42:29 -0700275 def onBuildCompleted(self, build, result, result_data):
James E. Blair71e94122012-12-24 17:53:08 -0800276 build.end_time = time.time()
James E. Blair196f61a2017-06-30 15:42:29 -0700277 build.result_data = result_data
James E. Blairf0358662015-07-20 15:19:12 -0700278 # Note, as soon as the result is set, other threads may act
279 # upon this, even though the event hasn't been fully
280 # processed. Ensure that any other data from the event (eg,
281 # timing) is recorded before setting the result.
282 build.result = result
James E. Blair23ec1ba2013-01-04 18:06:10 -0800283 try:
James E. Blair552b54f2016-07-22 13:55:32 -0700284 if self.statsd and build.pipeline:
James E. Blair66eeebf2013-07-27 17:44:32 -0700285 jobname = build.job.name.replace('.', '_')
Timothy Chavezb2332082015-08-07 20:08:04 -0500286 key = 'zuul.pipeline.%s.all_jobs' % build.pipeline.name
James E. Blair552b54f2016-07-22 13:55:32 -0700287 self.statsd.incr(key)
Timothy Chavezb2332082015-08-07 20:08:04 -0500288 for label in build.node_labels:
289 # Jenkins includes the node name in its list of labels, so
290 # we filter it out here, since that is not statistically
291 # interesting.
292 if label == build.node_name:
293 continue
Paul Belanger174a8272017-03-14 13:20:10 -0400294 dt = int((build.start_time - build.execute_time) * 1000)
James E. Blair50aacbc2015-11-17 14:09:59 -0800295 key = 'zuul.pipeline.%s.label.%s.wait_time' % (
296 build.pipeline.name, label)
James E. Blair552b54f2016-07-22 13:55:32 -0700297 self.statsd.timing(key, dt)
James E. Blair66eeebf2013-07-27 17:44:32 -0700298 key = 'zuul.pipeline.%s.job.%s.%s' % (build.pipeline.name,
299 jobname, build.result)
James E. Blair23ec1ba2013-01-04 18:06:10 -0800300 if build.result in ['SUCCESS', 'FAILURE'] and build.start_time:
301 dt = int((build.end_time - build.start_time) * 1000)
James E. Blair552b54f2016-07-22 13:55:32 -0700302 self.statsd.timing(key, dt)
303 self.statsd.incr(key)
James E. Blair50aacbc2015-11-17 14:09:59 -0800304
305 key = 'zuul.pipeline.%s.job.%s.wait_time' % (
306 build.pipeline.name, jobname)
Paul Belanger174a8272017-03-14 13:20:10 -0400307 dt = int((build.start_time - build.execute_time) * 1000)
James E. Blair552b54f2016-07-22 13:55:32 -0700308 self.statsd.timing(key, dt)
James E. Blair23ec1ba2013-01-04 18:06:10 -0800309 except:
310 self.log.exception("Exception reporting runtime stats")
James E. Blaira84f0e42014-02-06 07:09:22 -0800311 event = BuildCompletedEvent(build)
312 self.result_event_queue.put(event)
James E. Blairee743612012-05-29 14:49:32 -0700313 self.wake_event.set()
314
Tobias Henkel34ee0882017-07-31 22:26:12 +0200315 def onMergeCompleted(self, build_set, merged, updated,
James E. Blair1960d682017-04-28 15:44:14 -0700316 commit, files, repo_state):
Tobias Henkel34ee0882017-07-31 22:26:12 +0200317 event = MergeCompletedEvent(build_set, merged,
James E. Blair1960d682017-04-28 15:44:14 -0700318 updated, commit, files, repo_state)
James E. Blair4076e2b2014-01-28 12:42:20 -0800319 self.result_event_queue.put(event)
320 self.wake_event.set()
321
James E. Blair8d692392016-04-08 17:47:58 -0700322 def onNodesProvisioned(self, req):
James E. Blair8d692392016-04-08 17:47:58 -0700323 event = NodesProvisionedEvent(req)
324 self.result_event_queue.put(event)
325 self.wake_event.set()
326
James E. Blaira615c362017-10-02 17:34:42 -0700327 def reconfigureTenant(self, tenant, project):
328 self.log.debug("Submitting tenant reconfiguration event for "
329 "%s due to project %s", tenant.name, project)
330 event = TenantReconfigureEvent(tenant, project)
James E. Blair646322f2017-01-27 15:50:34 -0800331 self.management_event_queue.put(event)
332 self.wake_event.set()
333
James E. Blaire9d45c32012-05-31 09:56:45 -0700334 def reconfigure(self, config):
James E. Blaira615c362017-10-02 17:34:42 -0700335 self.log.debug("Submitting reconfiguration event")
James E. Blair468c8512013-12-06 13:27:19 -0800336 event = ReconfigureEvent(config)
337 self.management_event_queue.put(event)
James E. Blaire9d45c32012-05-31 09:56:45 -0700338 self.wake_event.set()
339 self.log.debug("Waiting for reconfiguration")
James E. Blair468c8512013-12-06 13:27:19 -0800340 event.wait()
James E. Blaire9d45c32012-05-31 09:56:45 -0700341 self.log.debug("Reconfiguration complete")
Sergey Lukjanov5d0438d2013-12-24 03:36:39 +0400342 self.last_reconfigured = int(time.time())
James E. Blair646322f2017-01-27 15:50:34 -0800343 # TODOv3(jeblair): reconfigure time should be per-tenant
James E. Blaire9d45c32012-05-31 09:56:45 -0700344
David Shrewsbury36b2adf2017-07-31 15:40:13 -0400345 def autohold(self, tenant_name, project_name, job_name, reason, count):
David Shrewsburyffab07a2017-07-24 12:45:07 -0400346 key = (tenant_name, project_name, job_name)
347 if count == 0 and key in self.autohold_requests:
348 self.log.debug("Removing autohold for %s", key)
349 del self.autohold_requests[key]
350 else:
351 self.log.debug("Autohold requested for %s", key)
David Shrewsbury36b2adf2017-07-31 15:40:13 -0400352 self.autohold_requests[key] = (count, reason)
David Shrewsburyffab07a2017-07-24 12:45:07 -0400353
Paul Belangerbaca3132016-11-04 12:49:54 -0400354 def promote(self, tenant_name, pipeline_name, change_ids):
355 event = PromoteEvent(tenant_name, pipeline_name, change_ids)
James E. Blair36658cf2013-12-06 17:53:48 -0800356 self.management_event_queue.put(event)
357 self.wake_event.set()
358 self.log.debug("Waiting for promotion")
359 event.wait()
360 self.log.debug("Promotion complete")
361
James E. Blaird27a96d2014-07-10 13:25:13 -0700362 def enqueue(self, trigger_event):
363 event = EnqueueEvent(trigger_event)
364 self.management_event_queue.put(event)
365 self.wake_event.set()
366 self.log.debug("Waiting for enqueue")
367 event.wait()
368 self.log.debug("Enqueue complete")
369
James E. Blair5d5bc2b2012-07-06 10:24:01 -0700370 def exit(self):
371 self.log.debug("Prepare to exit")
372 self._pause = True
373 self._exit = True
374 self.wake_event.set()
375 self.log.debug("Waiting for exit")
376
377 def _get_queue_pickle_file(self):
James E. Blaird1de9462017-06-23 20:53:09 +0100378 state_dir = get_default(self.config, 'scheduler', 'state_dir',
Tristan Cacqueray91601d72017-06-15 06:00:12 +0000379 '/var/lib/zuul', expand_user=True)
James E. Blair5d5bc2b2012-07-06 10:24:01 -0700380 return os.path.join(state_dir, 'queue.pickle')
381
James E. Blairce8a2132016-05-19 15:21:52 -0700382 def _get_time_database_dir(self):
James E. Blaird1de9462017-06-23 20:53:09 +0100383 state_dir = get_default(self.config, 'scheduler', 'state_dir',
Tristan Cacqueray91601d72017-06-15 06:00:12 +0000384 '/var/lib/zuul', expand_user=True)
James E. Blairce8a2132016-05-19 15:21:52 -0700385 d = os.path.join(state_dir, 'times')
386 if not os.path.exists(d):
387 os.mkdir(d)
388 return d
389
Ricardo Carrillo Cruz22994f92016-12-02 11:41:58 +0000390 def _get_project_key_dir(self):
James E. Blaird1de9462017-06-23 20:53:09 +0100391 state_dir = get_default(self.config, 'scheduler', 'state_dir',
Tristan Cacqueray91601d72017-06-15 06:00:12 +0000392 '/var/lib/zuul', expand_user=True)
Ricardo Carrillo Cruz22994f92016-12-02 11:41:58 +0000393 key_dir = os.path.join(state_dir, 'keys')
394 if not os.path.exists(key_dir):
395 os.mkdir(key_dir, 0o700)
396 st = os.stat(key_dir)
397 mode = st.st_mode & 0o777
398 if mode != 0o700:
399 raise Exception("Project key directory %s must be mode 0700; "
400 "current mode is %o" % (key_dir, mode))
401 return key_dir
402
James E. Blair5d5bc2b2012-07-06 10:24:01 -0700403 def _save_queue(self):
404 pickle_file = self._get_queue_pickle_file()
405 events = []
406 while not self.trigger_event_queue.empty():
407 events.append(self.trigger_event_queue.get())
408 self.log.debug("Queue length is %s" % len(events))
409 if events:
410 self.log.debug("Saving queue")
411 pickle.dump(events, open(pickle_file, 'wb'))
412
413 def _load_queue(self):
414 pickle_file = self._get_queue_pickle_file()
415 if os.path.exists(pickle_file):
416 self.log.debug("Loading queue")
417 events = pickle.load(open(pickle_file, 'rb'))
418 self.log.debug("Queue length is %s" % len(events))
419 for event in events:
420 self.trigger_event_queue.put(event)
421 else:
422 self.log.debug("No queue file found")
423
424 def _delete_queue(self):
425 pickle_file = self._get_queue_pickle_file()
426 if os.path.exists(pickle_file):
427 self.log.debug("Deleting saved queue")
428 os.unlink(pickle_file)
429
430 def resume(self):
431 try:
432 self._load_queue()
433 except:
434 self.log.exception("Unable to load queue")
435 try:
436 self._delete_queue()
437 except:
438 self.log.exception("Unable to delete saved queue")
439 self.log.debug("Resuming queue processing")
440 self.wake_event.set()
441
442 def _doPauseEvent(self):
443 if self._exit:
444 self.log.debug("Exiting")
445 self._save_queue()
446 os._exit(0)
James E. Blaircdccd972013-07-01 12:10:22 -0700447
James E. Blair468c8512013-12-06 13:27:19 -0800448 def _doReconfigureEvent(self, event):
449 # This is called in the scheduler loop after another thread submits
450 # a request
James E. Blaircdccd972013-07-01 12:10:22 -0700451 self.layout_lock.acquire()
James E. Blair468c8512013-12-06 13:27:19 -0800452 self.config = event.config
James E. Blaircdccd972013-07-01 12:10:22 -0700453 try:
James E. Blaira615c362017-10-02 17:34:42 -0700454 self.log.debug("Full reconfiguration beginning")
James E. Blair83005782015-12-11 14:46:03 -0800455 loader = configloader.ConfigLoader()
456 abide = loader.loadConfig(
James E. Blair39840362017-06-23 20:34:02 +0100457 self.config.get('scheduler', 'tenant_config'),
Ricardo Carrillo Cruz22994f92016-12-02 11:41:58 +0000458 self._get_project_key_dir(),
James E. Blair83005782015-12-11 14:46:03 -0800459 self, self.merger, self.connections)
James E. Blair59fdbac2015-12-07 17:08:06 -0800460 for tenant in abide.tenants.values():
461 self._reconfigureTenant(tenant)
462 self.abide = abide
James E. Blaircdccd972013-07-01 12:10:22 -0700463 finally:
464 self.layout_lock.release()
James E. Blaira615c362017-10-02 17:34:42 -0700465 self.log.debug("Full reconfiguration complete")
James E. Blaire9d45c32012-05-31 09:56:45 -0700466
James E. Blair646322f2017-01-27 15:50:34 -0800467 def _doTenantReconfigureEvent(self, event):
468 # This is called in the scheduler loop after another thread submits
469 # a request
470 self.layout_lock.acquire()
471 try:
James E. Blaira615c362017-10-02 17:34:42 -0700472 self.log.debug("Tenant reconfiguration beginning")
473 # If a change landed to a project, clear out the cached
474 # config before reconfiguring.
475 if event.project:
476 event.project.unparsed_config = None
James E. Blair646322f2017-01-27 15:50:34 -0800477 loader = configloader.ConfigLoader()
478 abide = loader.reloadTenant(
James E. Blair39840362017-06-23 20:34:02 +0100479 self.config.get('scheduler', 'tenant_config'),
Ricardo Carrillo Cruz22994f92016-12-02 11:41:58 +0000480 self._get_project_key_dir(),
James E. Blair646322f2017-01-27 15:50:34 -0800481 self, self.merger, self.connections,
482 self.abide, event.tenant)
483 tenant = abide.tenants[event.tenant.name]
484 self._reconfigureTenant(tenant)
485 self.abide = abide
486 finally:
487 self.layout_lock.release()
James E. Blaira615c362017-10-02 17:34:42 -0700488 self.log.debug("Tenant reconfiguration complete")
James E. Blair646322f2017-01-27 15:50:34 -0800489
James E. Blairaa30de42017-04-25 10:56:59 -0700490 def _reenqueueGetProject(self, tenant, item):
491 project = item.change.project
James E. Blair6053de42017-04-05 11:27:11 -0700492 # Attempt to get the same project as the one passed in. If
493 # the project is now found on a different connection, return
494 # the new version of the project. If it is no longer
495 # available (due to a connection being removed), return None.
James E. Blairaa30de42017-04-25 10:56:59 -0700496 (trusted, new_project) = tenant.getProject(project.canonical_name)
James E. Blair6053de42017-04-05 11:27:11 -0700497 if new_project:
498 return new_project
James E. Blairaa30de42017-04-25 10:56:59 -0700499 # If this is a non-live item we may be looking at a
500 # "foreign" project, ie, one which is not defined in the
501 # config but is constructed ad-hoc to satisfy a
502 # cross-repo-dependency. Find the corresponding live item
503 # and use its source.
504 child = item
505 while child and not child.live:
506 # This assumes that the queue does not branch behind this
507 # item, which is currently true for non-live items; if
508 # that changes, this traversal will need to be more
509 # complex.
510 if child.items_behind:
511 child = child.items_behind[0]
512 else:
513 child = None
514 if child is item:
515 return None
516 if child and child.live:
517 (child_trusted, child_project) = tenant.getProject(
518 child.change.project.canonical_name)
519 if child_project:
520 source = child_project.source
521 new_project = source.getProject(project.name)
522 return new_project
523 return None
James E. Blair6053de42017-04-05 11:27:11 -0700524
James E. Blair552b54f2016-07-22 13:55:32 -0700525 def _reenqueueTenant(self, old_tenant, tenant):
James E. Blair59fdbac2015-12-07 17:08:06 -0800526 for name, new_pipeline in tenant.layout.pipelines.items():
527 old_pipeline = old_tenant.layout.pipelines.get(name)
528 if not old_pipeline:
529 self.log.warning("No old pipeline matching %s found "
530 "when reconfiguring" % name)
531 continue
532 self.log.debug("Re-enqueueing changes for pipeline %s" % name)
533 items_to_remove = []
534 builds_to_cancel = []
535 last_head = None
536 for shared_queue in old_pipeline.queues:
537 for item in shared_queue.queue:
538 if not item.item_ahead:
539 last_head = item
James E. Blair59fdbac2015-12-07 17:08:06 -0800540 item.pipeline = None
541 item.queue = None
James E. Blair6053de42017-04-05 11:27:11 -0700542 item.change.project = self._reenqueueGetProject(
James E. Blairaa30de42017-04-25 10:56:59 -0700543 tenant, item)
544 item.item_ahead = None
545 item.items_behind = []
James E. Blair027ba992017-09-20 13:48:32 -0700546 reenqueued = False
547 if item.change.project:
548 try:
549 reenqueued = new_pipeline.manager.reEnqueueItem(
550 item, last_head)
551 except Exception:
552 self.log.exception(
553 "Exception while re-enqueing item %s",
554 item)
555 if reenqueued:
James E. Blair3b5ff3b2016-07-21 10:08:24 -0700556 for build in item.current_build_set.getBuilds():
Fredrik Medleyf8aec832015-09-28 13:40:20 +0200557 new_job = item.getJob(build.job.name)
558 if new_job:
559 build.job = new_job
James E. Blair3b5ff3b2016-07-21 10:08:24 -0700560 else:
561 item.removeBuild(build)
562 builds_to_cancel.append(build)
563 else:
James E. Blair59fdbac2015-12-07 17:08:06 -0800564 items_to_remove.append(item)
565 for item in items_to_remove:
James E. Blairb5a8f0b2017-07-07 17:01:18 -0700566 self.log.warning(
567 "Removing item %s during reconfiguration" % (item,))
James E. Blair59fdbac2015-12-07 17:08:06 -0800568 for build in item.current_build_set.getBuilds():
569 builds_to_cancel.append(build)
570 for build in builds_to_cancel:
571 self.log.warning(
572 "Canceling build %s during reconfiguration" % (build,))
573 try:
Paul Belanger174a8272017-03-14 13:20:10 -0400574 self.executor.cancel(build)
James E. Blair59fdbac2015-12-07 17:08:06 -0800575 except Exception:
576 self.log.exception(
577 "Exception while canceling build %s "
Tobias Henkel9a0e1942017-03-20 16:16:02 +0100578 "for change %s" % (build, build.build_set.item.change))
Tobias Henkelfb91a492017-02-15 07:29:43 +0100579 finally:
Tobias Henkel9a0e1942017-03-20 16:16:02 +0100580 tenant.semaphore_handler.release(
581 build.build_set.item, build.job)
James E. Blair552b54f2016-07-22 13:55:32 -0700582
583 def _reconfigureTenant(self, tenant):
584 # This is called from _doReconfigureEvent while holding the
585 # layout lock
586 old_tenant = self.abide.tenants.get(tenant.name)
Tobias Henkel9a0e1942017-03-20 16:16:02 +0100587
James E. Blair552b54f2016-07-22 13:55:32 -0700588 if old_tenant:
Tobias Henkel9a0e1942017-03-20 16:16:02 +0100589 # Copy over semaphore handler so we don't loose the currently
590 # held semaphores.
591 tenant.semaphore_handler = old_tenant.semaphore_handler
592
James E. Blair552b54f2016-07-22 13:55:32 -0700593 self._reenqueueTenant(old_tenant, tenant)
Tobias Henkel9a0e1942017-03-20 16:16:02 +0100594
James E. Blair25796c22017-09-08 09:34:37 -0700595 # TODOv3(jeblair): update for tenants
596 # self.maintainConnectionCache()
James E. Blaire511d2f2016-12-08 15:22:26 -0800597 self.connections.reconfigureDrivers(tenant)
Tobias Henkel9a0e1942017-03-20 16:16:02 +0100598
James E. Blaire511d2f2016-12-08 15:22:26 -0800599 # TODOv3(jeblair): remove postconfig calls?
James E. Blair59fdbac2015-12-07 17:08:06 -0800600 for pipeline in tenant.layout.pipelines.values():
James E. Blair552b54f2016-07-22 13:55:32 -0700601 for trigger in pipeline.triggers:
602 trigger.postConfig(pipeline)
James E. Blair83005782015-12-11 14:46:03 -0800603 for reporter in pipeline.actions:
604 reporter.postConfig()
Jesse Keating71a47ff2017-06-06 11:36:43 -0700605 self.tenant_last_reconfigured[tenant.name] = int(time.time())
James E. Blair552b54f2016-07-22 13:55:32 -0700606 if self.statsd:
James E. Blair59fdbac2015-12-07 17:08:06 -0800607 try:
James E. Blair552b54f2016-07-22 13:55:32 -0700608 for pipeline in tenant.layout.pipelines.values():
James E. Blair59fdbac2015-12-07 17:08:06 -0800609 items = len(pipeline.getAllItems())
610 # stats.gauges.zuul.pipeline.NAME.current_changes
611 key = 'zuul.pipeline.%s' % pipeline.name
James E. Blair552b54f2016-07-22 13:55:32 -0700612 self.statsd.gauge(key + '.current_changes', items)
James E. Blair59fdbac2015-12-07 17:08:06 -0800613 except Exception:
614 self.log.exception("Exception reporting initial "
615 "pipeline stats:")
616
James E. Blair36658cf2013-12-06 17:53:48 -0800617 def _doPromoteEvent(self, event):
Paul Belangerbaca3132016-11-04 12:49:54 -0400618 tenant = self.abide.tenants.get(event.tenant_name)
619 pipeline = tenant.layout.pipelines[event.pipeline_name]
James E. Blair36658cf2013-12-06 17:53:48 -0800620 change_ids = [c.split(',') for c in event.change_ids]
621 items_to_enqueue = []
622 change_queue = None
623 for shared_queue in pipeline.queues:
624 if change_queue:
625 break
626 for item in shared_queue.queue:
627 if (item.change.number == change_ids[0][0] and
Joshua Hesketh29d99b72014-08-19 16:27:42 +1000628 item.change.patchset == change_ids[0][1]):
James E. Blair36658cf2013-12-06 17:53:48 -0800629 change_queue = shared_queue
630 break
631 if not change_queue:
632 raise Exception("Unable to find shared change queue for %s" %
633 event.change_ids[0])
634 for number, patchset in change_ids:
635 found = False
636 for item in change_queue.queue:
637 if (item.change.number == number and
Joshua Hesketh29d99b72014-08-19 16:27:42 +1000638 item.change.patchset == patchset):
James E. Blair36658cf2013-12-06 17:53:48 -0800639 found = True
640 items_to_enqueue.append(item)
641 break
642 if not found:
643 raise Exception("Unable to find %s,%s in queue %s" %
644 (number, patchset, change_queue))
645 for item in change_queue.queue[:]:
646 if item not in items_to_enqueue:
647 items_to_enqueue.append(item)
648 pipeline.manager.cancelJobs(item)
649 pipeline.manager.dequeueItem(item)
650 for item in items_to_enqueue:
Sean Daguef39b9ca2014-01-10 21:34:35 -0500651 pipeline.manager.addChange(
652 item.change,
653 enqueue_time=item.enqueue_time,
James E. Blairf9ab8842014-07-10 13:12:07 -0700654 quiet=True,
655 ignore_requirements=True)
James E. Blair36658cf2013-12-06 17:53:48 -0800656
James E. Blaird27a96d2014-07-10 13:25:13 -0700657 def _doEnqueueEvent(self, event):
Paul Belangerbaca3132016-11-04 12:49:54 -0400658 tenant = self.abide.tenants.get(event.tenant_name)
James E. Blair0ffa0102017-03-30 13:11:33 -0700659 (trusted, project) = tenant.getProject(event.project_name)
Paul Belangerbaca3132016-11-04 12:49:54 -0400660 pipeline = tenant.layout.pipelines[event.forced_pipeline]
James E. Blair6053de42017-04-05 11:27:11 -0700661 change = project.source.getChange(event, project)
James E. Blaird27a96d2014-07-10 13:25:13 -0700662 self.log.debug("Event %s for change %s was directly assigned "
663 "to pipeline %s" % (event, change, self))
James E. Blaird27a96d2014-07-10 13:25:13 -0700664 pipeline.manager.addChange(change, ignore_requirements=True)
665
James E. Blaire9d45c32012-05-31 09:56:45 -0700666 def _areAllBuildsComplete(self):
667 self.log.debug("Checking if all builds are complete")
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700668 if self.merger.areMergesOutstanding():
669 self.log.debug("Waiting on merger")
670 return False
James E. Blaire9d45c32012-05-31 09:56:45 -0700671 waiting = False
Paul Belangerdebd7a72016-11-11 19:56:15 -0500672 for tenant in self.abide.tenants.values():
673 for pipeline in tenant.layout.pipelines.values():
674 for item in pipeline.getAllItems():
675 for build in item.current_build_set.getBuilds():
676 if build.result is None:
677 self.log.debug("%s waiting on %s" %
678 (pipeline.manager, build))
679 waiting = True
James E. Blaire9d45c32012-05-31 09:56:45 -0700680 if not waiting:
681 self.log.debug("All builds are complete")
682 return True
James E. Blaire9d45c32012-05-31 09:56:45 -0700683 return False
684
James E. Blairee743612012-05-29 14:49:32 -0700685 def run(self):
James E. Blair552b54f2016-07-22 13:55:32 -0700686 if self.statsd:
James E. Blair71e94122012-12-24 17:53:08 -0800687 self.log.debug("Statsd enabled")
688 else:
689 self.log.debug("Statsd disabled because python statsd "
690 "package not found")
James E. Blairee743612012-05-29 14:49:32 -0700691 while True:
692 self.log.debug("Run handler sleeping")
693 self.wake_event.wait()
694 self.wake_event.clear()
James E. Blairb0fcae42012-07-17 11:12:10 -0700695 if self._stopped:
James E. Blair4076e2b2014-01-28 12:42:20 -0800696 self.log.debug("Run handler stopping")
James E. Blairb0fcae42012-07-17 11:12:10 -0700697 return
James E. Blairee743612012-05-29 14:49:32 -0700698 self.log.debug("Run handler awake")
James E. Blaira84f0e42014-02-06 07:09:22 -0800699 self.run_handler_lock.acquire()
James E. Blairee743612012-05-29 14:49:32 -0700700 try:
James E. Blaira84f0e42014-02-06 07:09:22 -0800701 while not self.management_event_queue.empty():
James E. Blair468c8512013-12-06 13:27:19 -0800702 self.process_management_queue()
James E. Blaircdccd972013-07-01 12:10:22 -0700703
James E. Blair263fba92013-02-27 13:07:19 -0800704 # Give result events priority -- they let us stop builds,
Paul Belanger174a8272017-03-14 13:20:10 -0400705 # whereas trigger events cause us to execute builds.
James E. Blaira84f0e42014-02-06 07:09:22 -0800706 while not self.result_event_queue.empty():
James E. Blairee743612012-05-29 14:49:32 -0700707 self.process_result_queue()
James E. Blaira84f0e42014-02-06 07:09:22 -0800708
709 if not self._pause:
710 while not self.trigger_event_queue.empty():
James E. Blair263fba92013-02-27 13:07:19 -0800711 self.process_event_queue()
James E. Blaire9d45c32012-05-31 09:56:45 -0700712
James E. Blair5d5bc2b2012-07-06 10:24:01 -0700713 if self._pause and self._areAllBuildsComplete():
714 self._doPauseEvent()
James E. Blaire9d45c32012-05-31 09:56:45 -0700715
James E. Blair59fdbac2015-12-07 17:08:06 -0800716 for tenant in self.abide.tenants.values():
717 for pipeline in tenant.layout.pipelines.values():
718 while pipeline.manager.processQueue():
719 pass
James E. Blair0e933c52013-07-11 10:18:52 -0700720
James E. Blaira84f0e42014-02-06 07:09:22 -0800721 except Exception:
James E. Blairee743612012-05-29 14:49:32 -0700722 self.log.exception("Exception in run handler:")
James E. Blaira84f0e42014-02-06 07:09:22 -0800723 # There may still be more events to process
724 self.wake_event.set()
725 finally:
726 self.run_handler_lock.release()
James E. Blairee743612012-05-29 14:49:32 -0700727
Joshua Hesketh4bd7da32016-02-17 20:58:47 +1100728 def maintainConnectionCache(self):
James E. Blair25796c22017-09-08 09:34:37 -0700729 # TODOv3(jeblair): update for tenants
James E. Blair0e933c52013-07-11 10:18:52 -0700730 relevant = set()
James E. Blair59fdbac2015-12-07 17:08:06 -0800731 for tenant in self.abide.tenants.values():
732 for pipeline in tenant.layout.pipelines.values():
Joshua Heskethdc7820c2016-03-11 13:14:28 +1100733 self.log.debug("Gather relevant cache items for: %s" %
James E. Blair59fdbac2015-12-07 17:08:06 -0800734 pipeline)
Joshua Heskethdc7820c2016-03-11 13:14:28 +1100735
James E. Blair59fdbac2015-12-07 17:08:06 -0800736 for item in pipeline.getAllItems():
737 relevant.add(item.change)
738 relevant.update(item.change.getRelatedChanges())
James E. Blair25796c22017-09-08 09:34:37 -0700739 for connection in self.connections.values():
Joshua Hesketh4bd7da32016-02-17 20:58:47 +1100740 connection.maintainCache(relevant)
741 self.log.debug(
742 "End maintain connection cache for: %s" % connection)
743 self.log.debug("Connection cache size: %s" % len(relevant))
James E. Blair0e933c52013-07-11 10:18:52 -0700744
James E. Blairee743612012-05-29 14:49:32 -0700745 def process_event_queue(self):
746 self.log.debug("Fetching trigger event")
747 event = self.trigger_event_queue.get()
748 self.log.debug("Processing trigger event %s" % event)
James E. Blaira84f0e42014-02-06 07:09:22 -0800749 try:
James E. Blairaa30de42017-04-25 10:56:59 -0700750 full_project_name = ('/'.join([event.project_hostname,
751 event.project_name]))
James E. Blair59fdbac2015-12-07 17:08:06 -0800752 for tenant in self.abide.tenants.values():
James E. Blairaa30de42017-04-25 10:56:59 -0700753 (trusted, project) = tenant.getProject(full_project_name)
754 if project is None:
755 continue
756 try:
757 change = project.source.getChange(event)
758 except exceptions.ChangeNotFound as e:
759 self.log.debug("Unable to get change %s from "
760 "source %s",
761 e.change, project.source)
762 continue
James E. Blair72facdc2017-08-17 10:29:12 -0700763 if ((event.branch_updated and
764 hasattr(change, 'files') and
765 change.updatesConfig()) or
766 event.branch_created or
767 event.branch_deleted):
768 # The change that just landed updates the config
769 # or a branch was just created or deleted. Clear
770 # out cached data for this project and perform a
771 # reconfiguration.
James E. Blaira615c362017-10-02 17:34:42 -0700772 self.reconfigureTenant(tenant, change.project)
James E. Blair59fdbac2015-12-07 17:08:06 -0800773 for pipeline in tenant.layout.pipelines.values():
Jan Hruban324ca5b2015-11-05 19:28:54 +0100774 if event.isPatchsetCreated():
James E. Blair59fdbac2015-12-07 17:08:06 -0800775 pipeline.manager.removeOldVersionsOfChange(change)
Jan Hruban324ca5b2015-11-05 19:28:54 +0100776 elif event.isChangeAbandoned():
James E. Blair59fdbac2015-12-07 17:08:06 -0800777 pipeline.manager.removeAbandonedChange(change)
778 if pipeline.manager.eventMatches(event, change):
James E. Blair59fdbac2015-12-07 17:08:06 -0800779 pipeline.manager.addChange(change)
James E. Blaira84f0e42014-02-06 07:09:22 -0800780 finally:
James E. Blairff791972013-01-09 11:45:43 -0800781 self.trigger_event_queue.task_done()
James E. Blair1e8dd892012-05-30 09:15:05 -0700782
James E. Blair468c8512013-12-06 13:27:19 -0800783 def process_management_queue(self):
784 self.log.debug("Fetching management event")
785 event = self.management_event_queue.get()
786 self.log.debug("Processing management event %s" % event)
James E. Blair36658cf2013-12-06 17:53:48 -0800787 try:
788 if isinstance(event, ReconfigureEvent):
789 self._doReconfigureEvent(event)
James E. Blair21603e62017-02-20 16:23:05 -0500790 elif isinstance(event, TenantReconfigureEvent):
James E. Blair646322f2017-01-27 15:50:34 -0800791 self._doTenantReconfigureEvent(event)
James E. Blair36658cf2013-12-06 17:53:48 -0800792 elif isinstance(event, PromoteEvent):
793 self._doPromoteEvent(event)
James E. Blaird27a96d2014-07-10 13:25:13 -0700794 elif isinstance(event, EnqueueEvent):
795 self._doEnqueueEvent(event.trigger_event)
James E. Blair36658cf2013-12-06 17:53:48 -0800796 else:
797 self.log.error("Unable to handle event %s" % event)
798 event.done()
Morgan Fainberg1b9bd782016-05-30 14:03:30 -0700799 except Exception:
James E. Blair59424ea2017-07-11 09:52:58 -0700800 self.log.exception("Exception in management event:")
Morgan Fainberg1b9bd782016-05-30 14:03:30 -0700801 event.exception(sys.exc_info())
James E. Blair468c8512013-12-06 13:27:19 -0800802 self.management_event_queue.task_done()
803
James E. Blairee743612012-05-29 14:49:32 -0700804 def process_result_queue(self):
805 self.log.debug("Fetching result event")
James E. Blaira84f0e42014-02-06 07:09:22 -0800806 event = self.result_event_queue.get()
807 self.log.debug("Processing result event %s" % event)
808 try:
809 if isinstance(event, BuildStartedEvent):
810 self._doBuildStartedEvent(event)
811 elif isinstance(event, BuildCompletedEvent):
812 self._doBuildCompletedEvent(event)
James E. Blair4076e2b2014-01-28 12:42:20 -0800813 elif isinstance(event, MergeCompletedEvent):
814 self._doMergeCompletedEvent(event)
James E. Blair8d692392016-04-08 17:47:58 -0700815 elif isinstance(event, NodesProvisionedEvent):
816 self._doNodesProvisionedEvent(event)
James E. Blaira84f0e42014-02-06 07:09:22 -0800817 else:
818 self.log.error("Unable to handle event %s" % event)
819 finally:
820 self.result_event_queue.task_done()
821
822 def _doBuildStartedEvent(self, event):
James E. Blair4076e2b2014-01-28 12:42:20 -0800823 build = event.build
824 if build.build_set is not build.build_set.item.current_build_set:
825 self.log.warning("Build %s is not in the current build set" %
826 (build,))
827 return
828 pipeline = build.build_set.item.pipeline
829 if not pipeline:
830 self.log.warning("Build %s is not associated with a pipeline" %
831 (build,))
832 return
James E. Blairce8a2132016-05-19 15:21:52 -0700833 try:
834 build.estimated_time = float(self.time_database.getEstimatedTime(
James E. Blairae0f23c2017-09-13 10:55:15 -0600835 build))
James E. Blairce8a2132016-05-19 15:21:52 -0700836 except Exception:
837 self.log.exception("Exception estimating build time:")
James E. Blair4076e2b2014-01-28 12:42:20 -0800838 pipeline.manager.onBuildStarted(event.build)
James E. Blaira84f0e42014-02-06 07:09:22 -0800839
840 def _doBuildCompletedEvent(self, event):
James E. Blair4076e2b2014-01-28 12:42:20 -0800841 build = event.build
James E. Blaire18d4602017-01-05 11:17:28 -0800842
843 # Regardless of any other conditions which might cause us not
844 # to pass this on to the pipeline manager, make sure we return
845 # the nodes to nodepool.
846 try:
847 nodeset = build.build_set.getJobNodeSet(build.job.name)
David Shrewsburyffab07a2017-07-24 12:45:07 -0400848 autohold_key = (build.pipeline.layout.tenant.name,
849 build.build_set.item.change.project.canonical_name,
850 build.job.name)
851
852 try:
853 self.nodepool.holdNodeSet(nodeset, autohold_key)
854 except Exception:
855 self.log.exception("Unable to process autohold for %s",
856 autohold_key)
857
James E. Blair1511bc32017-01-18 09:25:31 -0800858 self.nodepool.returnNodeSet(nodeset)
James E. Blaire18d4602017-01-05 11:17:28 -0800859 except Exception:
860 self.log.exception("Unable to return nodeset %s" % (nodeset,))
861
James E. Blair4076e2b2014-01-28 12:42:20 -0800862 if build.build_set is not build.build_set.item.current_build_set:
James E. Blaire18d4602017-01-05 11:17:28 -0800863 self.log.debug("Build %s is not in the current build set" %
864 (build,))
James E. Blair4076e2b2014-01-28 12:42:20 -0800865 return
866 pipeline = build.build_set.item.pipeline
867 if not pipeline:
868 self.log.warning("Build %s is not associated with a pipeline" %
869 (build,))
870 return
James E. Blairce8a2132016-05-19 15:21:52 -0700871 if build.end_time and build.start_time and build.result:
872 duration = build.end_time - build.start_time
Paul Belanger87e4ab02016-06-08 14:17:20 -0400873 try:
James E. Blairae0f23c2017-09-13 10:55:15 -0600874 self.time_database.update(build, duration, build.result)
Paul Belanger87e4ab02016-06-08 14:17:20 -0400875 except Exception:
876 self.log.exception("Exception recording build time:")
James E. Blair4076e2b2014-01-28 12:42:20 -0800877 pipeline.manager.onBuildCompleted(event.build)
878
879 def _doMergeCompletedEvent(self, event):
880 build_set = event.build_set
881 if build_set is not build_set.item.current_build_set:
882 self.log.warning("Build set %s is not current" % (build_set,))
883 return
884 pipeline = build_set.item.pipeline
885 if not pipeline:
886 self.log.warning("Build set %s is not associated with a pipeline" %
887 (build_set,))
888 return
889 pipeline.manager.onMergeCompleted(event)
James E. Blairee743612012-05-29 14:49:32 -0700890
James E. Blair8d692392016-04-08 17:47:58 -0700891 def _doNodesProvisionedEvent(self, event):
892 request = event.request
David Shrewsbury94e95882017-10-04 15:26:04 -0400893 request_id = event.request_id
James E. Blair8d692392016-04-08 17:47:58 -0700894 build_set = request.build_set
James E. Blaira38c28e2017-01-04 10:33:20 -0800895
David Shrewsbury94e95882017-10-04 15:26:04 -0400896 self.nodepool.acceptNodes(request, request_id)
James E. Blaircbbce0d2017-05-19 07:28:29 -0700897 if request.canceled:
898 return
James E. Blaira38c28e2017-01-04 10:33:20 -0800899
James E. Blair8d692392016-04-08 17:47:58 -0700900 if build_set is not build_set.item.current_build_set:
901 self.log.warning("Build set %s is not current" % (build_set,))
James E. Blair6ab79e02017-01-06 10:10:17 -0800902 if request.fulfilled:
James E. Blair1511bc32017-01-18 09:25:31 -0800903 self.nodepool.returnNodeSet(request.nodeset)
James E. Blair8d692392016-04-08 17:47:58 -0700904 return
905 pipeline = build_set.item.pipeline
906 if not pipeline:
907 self.log.warning("Build set %s is not associated with a pipeline" %
908 (build_set,))
James E. Blair6ab79e02017-01-06 10:10:17 -0800909 if request.fulfilled:
James E. Blair1511bc32017-01-18 09:25:31 -0800910 self.nodepool.returnNodeSet(request.nodeset)
James E. Blair8d692392016-04-08 17:47:58 -0700911 return
912 pipeline.manager.onNodesProvisioned(event)
913
Paul Belanger6349d152016-10-30 16:21:17 -0400914 def formatStatusJSON(self, tenant_name):
James E. Blair59fdbac2015-12-07 17:08:06 -0800915 # TODOv3(jeblair): use tenants
James E. Blair8dbd56a2012-12-22 10:55:10 -0800916 data = {}
Sergey Lukjanov5ba961b2013-12-27 01:21:04 +0400917
918 data['zuul_version'] = self.zuul_version
Tobias Henkelb4407fc2017-07-07 13:52:56 +0200919 websocket_url = get_default(self.config, 'web', 'websocket_url', None)
Sergey Lukjanov5ba961b2013-12-27 01:21:04 +0400920
James E. Blair8dbd56a2012-12-22 10:55:10 -0800921 if self._pause:
922 ret = '<p><b>Queue only mode:</b> preparing to '
James E. Blair8dbd56a2012-12-22 10:55:10 -0800923 if self._exit:
924 ret += 'exit'
925 ret += ', queue length: %s' % self.trigger_event_queue.qsize()
926 ret += '</p>'
927 data['message'] = ret
928
James E. Blairfb682cc2013-02-26 15:23:27 -0800929 data['trigger_event_queue'] = {}
930 data['trigger_event_queue']['length'] = \
931 self.trigger_event_queue.qsize()
932 data['result_event_queue'] = {}
933 data['result_event_queue']['length'] = \
934 self.result_event_queue.qsize()
935
Sergey Lukjanov5d0438d2013-12-24 03:36:39 +0400936 if self.last_reconfigured:
937 data['last_reconfigured'] = self.last_reconfigured * 1000
938
James E. Blair8dbd56a2012-12-22 10:55:10 -0800939 pipelines = []
940 data['pipelines'] = pipelines
Paul Belanger6349d152016-10-30 16:21:17 -0400941 tenant = self.abide.tenants.get(tenant_name)
942 for pipeline in tenant.layout.pipelines.values():
Tobias Henkelb4407fc2017-07-07 13:52:56 +0200943 pipelines.append(pipeline.formatStatusJSON(websocket_url))
James E. Blair8dbd56a2012-12-22 10:55:10 -0800944 return json.dumps(data)