blob: c321bc09b07dcb0f2984fc7c3d7417a1e93f57c0 [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
167
168
Maru Newby3fe5f852015-01-13 04:22:14 +0000169def toList(item):
170 if not item:
171 return []
172 if isinstance(item, list):
173 return item
174 return [item]
175
176
James E. Blaire9d45c32012-05-31 09:56:45 -0700177class Scheduler(threading.Thread):
James E. Blaire4de4f42017-01-19 10:35:24 -0800178 """The engine of Zuul.
179
180 The Scheduler is reponsible for recieving events and dispatching
181 them to appropriate components (including pipeline managers,
Paul Belanger174a8272017-03-14 13:20:10 -0400182 mergers and executors).
James E. Blaire4de4f42017-01-19 10:35:24 -0800183
184 It runs a single threaded main loop which processes events
185 received one at a time and takes action as appropriate. Other
186 parts of Zuul may run in their own thread, but synchronization is
187 performed within the scheduler to reduce or eliminate the need for
188 locking in most circumstances.
189
190 The main daemon will have one instance of the Scheduler class
191 running which will persist for the life of the process. The
192 Scheduler instance is supplied to other Zuul components so that
193 they can submit events or otherwise communicate with other
194 components.
195
196 """
197
James E. Blairee743612012-05-29 14:49:32 -0700198 log = logging.getLogger("zuul.Scheduler")
199
James E. Blaire4d229c2016-05-25 15:25:41 -0700200 def __init__(self, config, testonly=False):
James E. Blaire9d45c32012-05-31 09:56:45 -0700201 threading.Thread.__init__(self)
James E. Blair8a6f0c22013-07-01 12:31:34 -0400202 self.daemon = True
James E. Blair8b2a1472017-02-19 15:33:55 -0800203 self.hostname = socket.gethostname()
James E. Blairee743612012-05-29 14:49:32 -0700204 self.wake_event = threading.Event()
James E. Blaircdccd972013-07-01 12:10:22 -0700205 self.layout_lock = threading.Lock()
James E. Blaira84f0e42014-02-06 07:09:22 -0800206 self.run_handler_lock = threading.Lock()
James E. Blair5d5bc2b2012-07-06 10:24:01 -0700207 self._pause = False
James E. Blair5d5bc2b2012-07-06 10:24:01 -0700208 self._exit = False
James E. Blairb0fcae42012-07-17 11:12:10 -0700209 self._stopped = False
Paul Belanger174a8272017-03-14 13:20:10 -0400210 self.executor = None
James E. Blair4076e2b2014-01-28 12:42:20 -0800211 self.merger = None
James E. Blair83005782015-12-11 14:46:03 -0800212 self.connections = None
James E. Blair552b54f2016-07-22 13:55:32 -0700213 self.statsd = extras.try_import('statsd.statsd')
James E. Blair83005782015-12-11 14:46:03 -0800214 # TODO(jeblair): fix this
Joshua Hesketh352264b2015-08-11 23:42:08 +1000215 # Despite triggers being part of the pipeline, there is one trigger set
216 # per scheduler. The pipeline handles the trigger filters but since
217 # the events are handled by the scheduler itself it needs to handle
218 # the loading of the triggers.
219 # self.triggers['connection_name'] = triggerObject
James E. Blair6c358e72013-07-29 17:06:47 -0700220 self.triggers = dict()
Joshua Hesketh352264b2015-08-11 23:42:08 +1000221 self.config = config
James E. Blairee743612012-05-29 14:49:32 -0700222
Monty Taylorb934c1a2017-06-16 19:31:47 -0500223 self.trigger_event_queue = queue.Queue()
224 self.result_event_queue = queue.Queue()
225 self.management_event_queue = queue.Queue()
James E. Blair59fdbac2015-12-07 17:08:06 -0800226 self.abide = model.Abide()
James E. Blairee743612012-05-29 14:49:32 -0700227
James E. Blaire4d229c2016-05-25 15:25:41 -0700228 if not testonly:
229 time_dir = self._get_time_database_dir()
230 self.time_database = model.TimeDataBase(time_dir)
James E. Blairce8a2132016-05-19 15:21:52 -0700231
Jeremy Stanley98b38de2015-06-04 21:20:43 +0000232 self.zuul_version = zuul_version.version_info.release_string()
Sergey Lukjanov5d0438d2013-12-24 03:36:39 +0400233 self.last_reconfigured = None
Jesse Keating71a47ff2017-06-06 11:36:43 -0700234 self.tenant_last_reconfigured = {}
David Shrewsburyffab07a2017-07-24 12:45:07 -0400235 self.autohold_requests = {}
Sergey Lukjanov5ba961b2013-12-27 01:21:04 +0400236
James E. Blairb0fcae42012-07-17 11:12:10 -0700237 def stop(self):
238 self._stopped = True
Joshua Hesketh352264b2015-08-11 23:42:08 +1000239 self.stopConnections()
James E. Blairb0fcae42012-07-17 11:12:10 -0700240 self.wake_event.set()
241
Jan Hruban7083edd2015-08-21 14:00:54 +0200242 def registerConnections(self, connections, webapp, load=True):
Joshua Hesketh9a256752016-04-04 13:38:51 +1000243 # load: whether or not to trigger the onLoad for the connection. This
244 # is useful for not doing a full load during layout validation.
Joshua Hesketh352264b2015-08-11 23:42:08 +1000245 self.connections = connections
Jan Hruban7083edd2015-08-21 14:00:54 +0200246 self.connections.registerWebapp(webapp)
Joshua Hesketh0aa7e8b2016-07-14 00:12:25 +1000247 self.connections.registerScheduler(self, load)
Joshua Hesketh352264b2015-08-11 23:42:08 +1000248
249 def stopConnections(self):
James E. Blair83005782015-12-11 14:46:03 -0800250 self.connections.stop()
James E. Blair14abdf42015-12-09 16:11:53 -0800251
Paul Belanger174a8272017-03-14 13:20:10 -0400252 def setExecutor(self, executor):
253 self.executor = executor
James E. Blairee743612012-05-29 14:49:32 -0700254
James E. Blair4076e2b2014-01-28 12:42:20 -0800255 def setMerger(self, merger):
256 self.merger = merger
257
James E. Blair8d692392016-04-08 17:47:58 -0700258 def setNodepool(self, nodepool):
259 self.nodepool = nodepool
260
James E. Blairdce6cea2016-12-20 16:45:32 -0800261 def setZooKeeper(self, zk):
262 self.zk = zk
263
James E. Blairee743612012-05-29 14:49:32 -0700264 def addEvent(self, event):
265 self.log.debug("Adding trigger event: %s" % event)
266 self.trigger_event_queue.put(event)
267 self.wake_event.set()
James E. Blairf62d4282012-12-31 17:01:50 -0800268 self.log.debug("Done adding trigger event: %s" % event)
James E. Blairee743612012-05-29 14:49:32 -0700269
James E. Blair11700c32012-07-05 17:50:05 -0700270 def onBuildStarted(self, build):
271 self.log.debug("Adding start event for build: %s" % build)
James E. Blair71e94122012-12-24 17:53:08 -0800272 build.start_time = time.time()
James E. Blaira84f0e42014-02-06 07:09:22 -0800273 event = BuildStartedEvent(build)
274 self.result_event_queue.put(event)
James E. Blair11700c32012-07-05 17:50:05 -0700275 self.wake_event.set()
James E. Blairf62d4282012-12-31 17:01:50 -0800276 self.log.debug("Done adding start event for build: %s" % build)
James E. Blair11700c32012-07-05 17:50:05 -0700277
James E. Blair196f61a2017-06-30 15:42:29 -0700278 def onBuildCompleted(self, build, result, result_data):
James E. Blairf0358662015-07-20 15:19:12 -0700279 self.log.debug("Adding complete event for build: %s result: %s" % (
280 build, result))
James E. Blair71e94122012-12-24 17:53:08 -0800281 build.end_time = time.time()
James E. Blair196f61a2017-06-30 15:42:29 -0700282 build.result_data = result_data
James E. Blairf0358662015-07-20 15:19:12 -0700283 # Note, as soon as the result is set, other threads may act
284 # upon this, even though the event hasn't been fully
285 # processed. Ensure that any other data from the event (eg,
286 # timing) is recorded before setting the result.
287 build.result = result
James E. Blair23ec1ba2013-01-04 18:06:10 -0800288 try:
James E. Blair552b54f2016-07-22 13:55:32 -0700289 if self.statsd and build.pipeline:
James E. Blair66eeebf2013-07-27 17:44:32 -0700290 jobname = build.job.name.replace('.', '_')
Timothy Chavezb2332082015-08-07 20:08:04 -0500291 key = 'zuul.pipeline.%s.all_jobs' % build.pipeline.name
James E. Blair552b54f2016-07-22 13:55:32 -0700292 self.statsd.incr(key)
Timothy Chavezb2332082015-08-07 20:08:04 -0500293 for label in build.node_labels:
294 # Jenkins includes the node name in its list of labels, so
295 # we filter it out here, since that is not statistically
296 # interesting.
297 if label == build.node_name:
298 continue
Paul Belanger174a8272017-03-14 13:20:10 -0400299 dt = int((build.start_time - build.execute_time) * 1000)
James E. Blair50aacbc2015-11-17 14:09:59 -0800300 key = 'zuul.pipeline.%s.label.%s.wait_time' % (
301 build.pipeline.name, label)
James E. Blair552b54f2016-07-22 13:55:32 -0700302 self.statsd.timing(key, dt)
James E. Blair66eeebf2013-07-27 17:44:32 -0700303 key = 'zuul.pipeline.%s.job.%s.%s' % (build.pipeline.name,
304 jobname, build.result)
James E. Blair23ec1ba2013-01-04 18:06:10 -0800305 if build.result in ['SUCCESS', 'FAILURE'] and build.start_time:
306 dt = int((build.end_time - build.start_time) * 1000)
James E. Blair552b54f2016-07-22 13:55:32 -0700307 self.statsd.timing(key, dt)
308 self.statsd.incr(key)
James E. Blair50aacbc2015-11-17 14:09:59 -0800309
310 key = 'zuul.pipeline.%s.job.%s.wait_time' % (
311 build.pipeline.name, jobname)
Paul Belanger174a8272017-03-14 13:20:10 -0400312 dt = int((build.start_time - build.execute_time) * 1000)
James E. Blair552b54f2016-07-22 13:55:32 -0700313 self.statsd.timing(key, dt)
James E. Blair23ec1ba2013-01-04 18:06:10 -0800314 except:
315 self.log.exception("Exception reporting runtime stats")
James E. Blaira84f0e42014-02-06 07:09:22 -0800316 event = BuildCompletedEvent(build)
317 self.result_event_queue.put(event)
James E. Blairee743612012-05-29 14:49:32 -0700318 self.wake_event.set()
James E. Blairf62d4282012-12-31 17:01:50 -0800319 self.log.debug("Done adding complete event for build: %s" % build)
James E. Blairee743612012-05-29 14:49:32 -0700320
Tobias Henkel34ee0882017-07-31 22:26:12 +0200321 def onMergeCompleted(self, build_set, merged, updated,
James E. Blair1960d682017-04-28 15:44:14 -0700322 commit, files, repo_state):
James E. Blair4076e2b2014-01-28 12:42:20 -0800323 self.log.debug("Adding merge complete event for build set: %s" %
324 build_set)
Tobias Henkel34ee0882017-07-31 22:26:12 +0200325 event = MergeCompletedEvent(build_set, merged,
James E. Blair1960d682017-04-28 15:44:14 -0700326 updated, commit, files, repo_state)
James E. Blair4076e2b2014-01-28 12:42:20 -0800327 self.result_event_queue.put(event)
328 self.wake_event.set()
329
James E. Blair8d692392016-04-08 17:47:58 -0700330 def onNodesProvisioned(self, req):
331 self.log.debug("Adding nodes provisioned event for build set: %s" %
332 req.build_set)
333 event = NodesProvisionedEvent(req)
334 self.result_event_queue.put(event)
335 self.wake_event.set()
336
James E. Blaira615c362017-10-02 17:34:42 -0700337 def reconfigureTenant(self, tenant, project):
338 self.log.debug("Submitting tenant reconfiguration event for "
339 "%s due to project %s", tenant.name, project)
340 event = TenantReconfigureEvent(tenant, project)
James E. Blair646322f2017-01-27 15:50:34 -0800341 self.management_event_queue.put(event)
342 self.wake_event.set()
343
James E. Blaire9d45c32012-05-31 09:56:45 -0700344 def reconfigure(self, config):
James E. Blaira615c362017-10-02 17:34:42 -0700345 self.log.debug("Submitting reconfiguration event")
James E. Blair468c8512013-12-06 13:27:19 -0800346 event = ReconfigureEvent(config)
347 self.management_event_queue.put(event)
James E. Blaire9d45c32012-05-31 09:56:45 -0700348 self.wake_event.set()
349 self.log.debug("Waiting for reconfiguration")
James E. Blair468c8512013-12-06 13:27:19 -0800350 event.wait()
James E. Blaire9d45c32012-05-31 09:56:45 -0700351 self.log.debug("Reconfiguration complete")
Sergey Lukjanov5d0438d2013-12-24 03:36:39 +0400352 self.last_reconfigured = int(time.time())
James E. Blair646322f2017-01-27 15:50:34 -0800353 # TODOv3(jeblair): reconfigure time should be per-tenant
James E. Blaire9d45c32012-05-31 09:56:45 -0700354
David Shrewsbury36b2adf2017-07-31 15:40:13 -0400355 def autohold(self, tenant_name, project_name, job_name, reason, count):
David Shrewsburyffab07a2017-07-24 12:45:07 -0400356 key = (tenant_name, project_name, job_name)
357 if count == 0 and key in self.autohold_requests:
358 self.log.debug("Removing autohold for %s", key)
359 del self.autohold_requests[key]
360 else:
361 self.log.debug("Autohold requested for %s", key)
David Shrewsbury36b2adf2017-07-31 15:40:13 -0400362 self.autohold_requests[key] = (count, reason)
David Shrewsburyffab07a2017-07-24 12:45:07 -0400363
Paul Belangerbaca3132016-11-04 12:49:54 -0400364 def promote(self, tenant_name, pipeline_name, change_ids):
365 event = PromoteEvent(tenant_name, pipeline_name, change_ids)
James E. Blair36658cf2013-12-06 17:53:48 -0800366 self.management_event_queue.put(event)
367 self.wake_event.set()
368 self.log.debug("Waiting for promotion")
369 event.wait()
370 self.log.debug("Promotion complete")
371
James E. Blaird27a96d2014-07-10 13:25:13 -0700372 def enqueue(self, trigger_event):
373 event = EnqueueEvent(trigger_event)
374 self.management_event_queue.put(event)
375 self.wake_event.set()
376 self.log.debug("Waiting for enqueue")
377 event.wait()
378 self.log.debug("Enqueue complete")
379
James E. Blair5d5bc2b2012-07-06 10:24:01 -0700380 def exit(self):
381 self.log.debug("Prepare to exit")
382 self._pause = True
383 self._exit = True
384 self.wake_event.set()
385 self.log.debug("Waiting for exit")
386
387 def _get_queue_pickle_file(self):
James E. Blaird1de9462017-06-23 20:53:09 +0100388 state_dir = get_default(self.config, 'scheduler', 'state_dir',
Tristan Cacqueray91601d72017-06-15 06:00:12 +0000389 '/var/lib/zuul', expand_user=True)
James E. Blair5d5bc2b2012-07-06 10:24:01 -0700390 return os.path.join(state_dir, 'queue.pickle')
391
James E. Blairce8a2132016-05-19 15:21:52 -0700392 def _get_time_database_dir(self):
James E. Blaird1de9462017-06-23 20:53:09 +0100393 state_dir = get_default(self.config, 'scheduler', 'state_dir',
Tristan Cacqueray91601d72017-06-15 06:00:12 +0000394 '/var/lib/zuul', expand_user=True)
James E. Blairce8a2132016-05-19 15:21:52 -0700395 d = os.path.join(state_dir, 'times')
396 if not os.path.exists(d):
397 os.mkdir(d)
398 return d
399
Ricardo Carrillo Cruz22994f92016-12-02 11:41:58 +0000400 def _get_project_key_dir(self):
James E. Blaird1de9462017-06-23 20:53:09 +0100401 state_dir = get_default(self.config, 'scheduler', 'state_dir',
Tristan Cacqueray91601d72017-06-15 06:00:12 +0000402 '/var/lib/zuul', expand_user=True)
Ricardo Carrillo Cruz22994f92016-12-02 11:41:58 +0000403 key_dir = os.path.join(state_dir, 'keys')
404 if not os.path.exists(key_dir):
405 os.mkdir(key_dir, 0o700)
406 st = os.stat(key_dir)
407 mode = st.st_mode & 0o777
408 if mode != 0o700:
409 raise Exception("Project key directory %s must be mode 0700; "
410 "current mode is %o" % (key_dir, mode))
411 return key_dir
412
James E. Blair5d5bc2b2012-07-06 10:24:01 -0700413 def _save_queue(self):
414 pickle_file = self._get_queue_pickle_file()
415 events = []
416 while not self.trigger_event_queue.empty():
417 events.append(self.trigger_event_queue.get())
418 self.log.debug("Queue length is %s" % len(events))
419 if events:
420 self.log.debug("Saving queue")
421 pickle.dump(events, open(pickle_file, 'wb'))
422
423 def _load_queue(self):
424 pickle_file = self._get_queue_pickle_file()
425 if os.path.exists(pickle_file):
426 self.log.debug("Loading queue")
427 events = pickle.load(open(pickle_file, 'rb'))
428 self.log.debug("Queue length is %s" % len(events))
429 for event in events:
430 self.trigger_event_queue.put(event)
431 else:
432 self.log.debug("No queue file found")
433
434 def _delete_queue(self):
435 pickle_file = self._get_queue_pickle_file()
436 if os.path.exists(pickle_file):
437 self.log.debug("Deleting saved queue")
438 os.unlink(pickle_file)
439
440 def resume(self):
441 try:
442 self._load_queue()
443 except:
444 self.log.exception("Unable to load queue")
445 try:
446 self._delete_queue()
447 except:
448 self.log.exception("Unable to delete saved queue")
449 self.log.debug("Resuming queue processing")
450 self.wake_event.set()
451
452 def _doPauseEvent(self):
453 if self._exit:
454 self.log.debug("Exiting")
455 self._save_queue()
456 os._exit(0)
James E. Blaircdccd972013-07-01 12:10:22 -0700457
James E. Blair468c8512013-12-06 13:27:19 -0800458 def _doReconfigureEvent(self, event):
459 # This is called in the scheduler loop after another thread submits
460 # a request
James E. Blaircdccd972013-07-01 12:10:22 -0700461 self.layout_lock.acquire()
James E. Blair468c8512013-12-06 13:27:19 -0800462 self.config = event.config
James E. Blaircdccd972013-07-01 12:10:22 -0700463 try:
James E. Blaira615c362017-10-02 17:34:42 -0700464 self.log.debug("Full reconfiguration beginning")
James E. Blair83005782015-12-11 14:46:03 -0800465 loader = configloader.ConfigLoader()
466 abide = loader.loadConfig(
James E. Blair39840362017-06-23 20:34:02 +0100467 self.config.get('scheduler', 'tenant_config'),
Ricardo Carrillo Cruz22994f92016-12-02 11:41:58 +0000468 self._get_project_key_dir(),
James E. Blair83005782015-12-11 14:46:03 -0800469 self, self.merger, self.connections)
James E. Blair59fdbac2015-12-07 17:08:06 -0800470 for tenant in abide.tenants.values():
471 self._reconfigureTenant(tenant)
472 self.abide = abide
James E. Blaircdccd972013-07-01 12:10:22 -0700473 finally:
474 self.layout_lock.release()
James E. Blaira615c362017-10-02 17:34:42 -0700475 self.log.debug("Full reconfiguration complete")
James E. Blaire9d45c32012-05-31 09:56:45 -0700476
James E. Blair646322f2017-01-27 15:50:34 -0800477 def _doTenantReconfigureEvent(self, event):
478 # This is called in the scheduler loop after another thread submits
479 # a request
480 self.layout_lock.acquire()
481 try:
James E. Blaira615c362017-10-02 17:34:42 -0700482 self.log.debug("Tenant reconfiguration beginning")
483 # If a change landed to a project, clear out the cached
484 # config before reconfiguring.
485 if event.project:
486 event.project.unparsed_config = None
James E. Blair646322f2017-01-27 15:50:34 -0800487 loader = configloader.ConfigLoader()
488 abide = loader.reloadTenant(
James E. Blair39840362017-06-23 20:34:02 +0100489 self.config.get('scheduler', 'tenant_config'),
Ricardo Carrillo Cruz22994f92016-12-02 11:41:58 +0000490 self._get_project_key_dir(),
James E. Blair646322f2017-01-27 15:50:34 -0800491 self, self.merger, self.connections,
492 self.abide, event.tenant)
493 tenant = abide.tenants[event.tenant.name]
494 self._reconfigureTenant(tenant)
495 self.abide = abide
496 finally:
497 self.layout_lock.release()
James E. Blaira615c362017-10-02 17:34:42 -0700498 self.log.debug("Tenant reconfiguration complete")
James E. Blair646322f2017-01-27 15:50:34 -0800499
James E. Blairaa30de42017-04-25 10:56:59 -0700500 def _reenqueueGetProject(self, tenant, item):
501 project = item.change.project
James E. Blair6053de42017-04-05 11:27:11 -0700502 # Attempt to get the same project as the one passed in. If
503 # the project is now found on a different connection, return
504 # the new version of the project. If it is no longer
505 # available (due to a connection being removed), return None.
James E. Blairaa30de42017-04-25 10:56:59 -0700506 (trusted, new_project) = tenant.getProject(project.canonical_name)
James E. Blair6053de42017-04-05 11:27:11 -0700507 if new_project:
508 return new_project
James E. Blairaa30de42017-04-25 10:56:59 -0700509 # If this is a non-live item we may be looking at a
510 # "foreign" project, ie, one which is not defined in the
511 # config but is constructed ad-hoc to satisfy a
512 # cross-repo-dependency. Find the corresponding live item
513 # and use its source.
514 child = item
515 while child and not child.live:
516 # This assumes that the queue does not branch behind this
517 # item, which is currently true for non-live items; if
518 # that changes, this traversal will need to be more
519 # complex.
520 if child.items_behind:
521 child = child.items_behind[0]
522 else:
523 child = None
524 if child is item:
525 return None
526 if child and child.live:
527 (child_trusted, child_project) = tenant.getProject(
528 child.change.project.canonical_name)
529 if child_project:
530 source = child_project.source
531 new_project = source.getProject(project.name)
532 return new_project
533 return None
James E. Blair6053de42017-04-05 11:27:11 -0700534
James E. Blair552b54f2016-07-22 13:55:32 -0700535 def _reenqueueTenant(self, old_tenant, tenant):
James E. Blair59fdbac2015-12-07 17:08:06 -0800536 for name, new_pipeline in tenant.layout.pipelines.items():
537 old_pipeline = old_tenant.layout.pipelines.get(name)
538 if not old_pipeline:
539 self.log.warning("No old pipeline matching %s found "
540 "when reconfiguring" % name)
541 continue
542 self.log.debug("Re-enqueueing changes for pipeline %s" % name)
543 items_to_remove = []
544 builds_to_cancel = []
545 last_head = None
546 for shared_queue in old_pipeline.queues:
547 for item in shared_queue.queue:
548 if not item.item_ahead:
549 last_head = item
James E. Blair59fdbac2015-12-07 17:08:06 -0800550 item.pipeline = None
551 item.queue = None
James E. Blair6053de42017-04-05 11:27:11 -0700552 item.change.project = self._reenqueueGetProject(
James E. Blairaa30de42017-04-25 10:56:59 -0700553 tenant, item)
554 item.item_ahead = None
555 item.items_behind = []
James E. Blair027ba992017-09-20 13:48:32 -0700556 reenqueued = False
557 if item.change.project:
558 try:
559 reenqueued = new_pipeline.manager.reEnqueueItem(
560 item, last_head)
561 except Exception:
562 self.log.exception(
563 "Exception while re-enqueing item %s",
564 item)
565 if reenqueued:
James E. Blair3b5ff3b2016-07-21 10:08:24 -0700566 for build in item.current_build_set.getBuilds():
Fredrik Medleyf8aec832015-09-28 13:40:20 +0200567 new_job = item.getJob(build.job.name)
568 if new_job:
569 build.job = new_job
James E. Blair3b5ff3b2016-07-21 10:08:24 -0700570 else:
571 item.removeBuild(build)
572 builds_to_cancel.append(build)
573 else:
James E. Blair59fdbac2015-12-07 17:08:06 -0800574 items_to_remove.append(item)
575 for item in items_to_remove:
James E. Blairb5a8f0b2017-07-07 17:01:18 -0700576 self.log.warning(
577 "Removing item %s during reconfiguration" % (item,))
James E. Blair59fdbac2015-12-07 17:08:06 -0800578 for build in item.current_build_set.getBuilds():
579 builds_to_cancel.append(build)
580 for build in builds_to_cancel:
581 self.log.warning(
582 "Canceling build %s during reconfiguration" % (build,))
583 try:
Paul Belanger174a8272017-03-14 13:20:10 -0400584 self.executor.cancel(build)
James E. Blair59fdbac2015-12-07 17:08:06 -0800585 except Exception:
586 self.log.exception(
587 "Exception while canceling build %s "
Tobias Henkel9a0e1942017-03-20 16:16:02 +0100588 "for change %s" % (build, build.build_set.item.change))
Tobias Henkelfb91a492017-02-15 07:29:43 +0100589 finally:
Tobias Henkel9a0e1942017-03-20 16:16:02 +0100590 tenant.semaphore_handler.release(
591 build.build_set.item, build.job)
James E. Blair552b54f2016-07-22 13:55:32 -0700592
593 def _reconfigureTenant(self, tenant):
594 # This is called from _doReconfigureEvent while holding the
595 # layout lock
596 old_tenant = self.abide.tenants.get(tenant.name)
Tobias Henkel9a0e1942017-03-20 16:16:02 +0100597
James E. Blair552b54f2016-07-22 13:55:32 -0700598 if old_tenant:
Tobias Henkel9a0e1942017-03-20 16:16:02 +0100599 # Copy over semaphore handler so we don't loose the currently
600 # held semaphores.
601 tenant.semaphore_handler = old_tenant.semaphore_handler
602
James E. Blair552b54f2016-07-22 13:55:32 -0700603 self._reenqueueTenant(old_tenant, tenant)
Tobias Henkel9a0e1942017-03-20 16:16:02 +0100604
James E. Blair25796c22017-09-08 09:34:37 -0700605 # TODOv3(jeblair): update for tenants
606 # self.maintainConnectionCache()
James E. Blaire511d2f2016-12-08 15:22:26 -0800607 self.connections.reconfigureDrivers(tenant)
Tobias Henkel9a0e1942017-03-20 16:16:02 +0100608
James E. Blaire511d2f2016-12-08 15:22:26 -0800609 # TODOv3(jeblair): remove postconfig calls?
James E. Blair59fdbac2015-12-07 17:08:06 -0800610 for pipeline in tenant.layout.pipelines.values():
James E. Blair552b54f2016-07-22 13:55:32 -0700611 for trigger in pipeline.triggers:
612 trigger.postConfig(pipeline)
James E. Blair83005782015-12-11 14:46:03 -0800613 for reporter in pipeline.actions:
614 reporter.postConfig()
Jesse Keating71a47ff2017-06-06 11:36:43 -0700615 self.tenant_last_reconfigured[tenant.name] = int(time.time())
James E. Blair552b54f2016-07-22 13:55:32 -0700616 if self.statsd:
James E. Blair59fdbac2015-12-07 17:08:06 -0800617 try:
James E. Blair552b54f2016-07-22 13:55:32 -0700618 for pipeline in tenant.layout.pipelines.values():
James E. Blair59fdbac2015-12-07 17:08:06 -0800619 items = len(pipeline.getAllItems())
620 # stats.gauges.zuul.pipeline.NAME.current_changes
621 key = 'zuul.pipeline.%s' % pipeline.name
James E. Blair552b54f2016-07-22 13:55:32 -0700622 self.statsd.gauge(key + '.current_changes', items)
James E. Blair59fdbac2015-12-07 17:08:06 -0800623 except Exception:
624 self.log.exception("Exception reporting initial "
625 "pipeline stats:")
626
James E. Blair36658cf2013-12-06 17:53:48 -0800627 def _doPromoteEvent(self, event):
Paul Belangerbaca3132016-11-04 12:49:54 -0400628 tenant = self.abide.tenants.get(event.tenant_name)
629 pipeline = tenant.layout.pipelines[event.pipeline_name]
James E. Blair36658cf2013-12-06 17:53:48 -0800630 change_ids = [c.split(',') for c in event.change_ids]
631 items_to_enqueue = []
632 change_queue = None
633 for shared_queue in pipeline.queues:
634 if change_queue:
635 break
636 for item in shared_queue.queue:
637 if (item.change.number == change_ids[0][0] and
Joshua Hesketh29d99b72014-08-19 16:27:42 +1000638 item.change.patchset == change_ids[0][1]):
James E. Blair36658cf2013-12-06 17:53:48 -0800639 change_queue = shared_queue
640 break
641 if not change_queue:
642 raise Exception("Unable to find shared change queue for %s" %
643 event.change_ids[0])
644 for number, patchset in change_ids:
645 found = False
646 for item in change_queue.queue:
647 if (item.change.number == number and
Joshua Hesketh29d99b72014-08-19 16:27:42 +1000648 item.change.patchset == patchset):
James E. Blair36658cf2013-12-06 17:53:48 -0800649 found = True
650 items_to_enqueue.append(item)
651 break
652 if not found:
653 raise Exception("Unable to find %s,%s in queue %s" %
654 (number, patchset, change_queue))
655 for item in change_queue.queue[:]:
656 if item not in items_to_enqueue:
657 items_to_enqueue.append(item)
658 pipeline.manager.cancelJobs(item)
659 pipeline.manager.dequeueItem(item)
660 for item in items_to_enqueue:
Sean Daguef39b9ca2014-01-10 21:34:35 -0500661 pipeline.manager.addChange(
662 item.change,
663 enqueue_time=item.enqueue_time,
James E. Blairf9ab8842014-07-10 13:12:07 -0700664 quiet=True,
665 ignore_requirements=True)
James E. Blair36658cf2013-12-06 17:53:48 -0800666
James E. Blaird27a96d2014-07-10 13:25:13 -0700667 def _doEnqueueEvent(self, event):
Paul Belangerbaca3132016-11-04 12:49:54 -0400668 tenant = self.abide.tenants.get(event.tenant_name)
James E. Blair0ffa0102017-03-30 13:11:33 -0700669 (trusted, project) = tenant.getProject(event.project_name)
Paul Belangerbaca3132016-11-04 12:49:54 -0400670 pipeline = tenant.layout.pipelines[event.forced_pipeline]
James E. Blair6053de42017-04-05 11:27:11 -0700671 change = project.source.getChange(event, project)
James E. Blaird27a96d2014-07-10 13:25:13 -0700672 self.log.debug("Event %s for change %s was directly assigned "
673 "to pipeline %s" % (event, change, self))
James E. Blaird27a96d2014-07-10 13:25:13 -0700674 pipeline.manager.addChange(change, ignore_requirements=True)
675
James E. Blaire9d45c32012-05-31 09:56:45 -0700676 def _areAllBuildsComplete(self):
677 self.log.debug("Checking if all builds are complete")
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700678 if self.merger.areMergesOutstanding():
679 self.log.debug("Waiting on merger")
680 return False
James E. Blaire9d45c32012-05-31 09:56:45 -0700681 waiting = False
Paul Belangerdebd7a72016-11-11 19:56:15 -0500682 for tenant in self.abide.tenants.values():
683 for pipeline in tenant.layout.pipelines.values():
684 for item in pipeline.getAllItems():
685 for build in item.current_build_set.getBuilds():
686 if build.result is None:
687 self.log.debug("%s waiting on %s" %
688 (pipeline.manager, build))
689 waiting = True
James E. Blaire9d45c32012-05-31 09:56:45 -0700690 if not waiting:
691 self.log.debug("All builds are complete")
692 return True
James E. Blaire9d45c32012-05-31 09:56:45 -0700693 return False
694
James E. Blairee743612012-05-29 14:49:32 -0700695 def run(self):
James E. Blair552b54f2016-07-22 13:55:32 -0700696 if self.statsd:
James E. Blair71e94122012-12-24 17:53:08 -0800697 self.log.debug("Statsd enabled")
698 else:
699 self.log.debug("Statsd disabled because python statsd "
700 "package not found")
James E. Blairee743612012-05-29 14:49:32 -0700701 while True:
702 self.log.debug("Run handler sleeping")
703 self.wake_event.wait()
704 self.wake_event.clear()
James E. Blairb0fcae42012-07-17 11:12:10 -0700705 if self._stopped:
James E. Blair4076e2b2014-01-28 12:42:20 -0800706 self.log.debug("Run handler stopping")
James E. Blairb0fcae42012-07-17 11:12:10 -0700707 return
James E. Blairee743612012-05-29 14:49:32 -0700708 self.log.debug("Run handler awake")
James E. Blaira84f0e42014-02-06 07:09:22 -0800709 self.run_handler_lock.acquire()
James E. Blairee743612012-05-29 14:49:32 -0700710 try:
James E. Blaira84f0e42014-02-06 07:09:22 -0800711 while not self.management_event_queue.empty():
James E. Blair468c8512013-12-06 13:27:19 -0800712 self.process_management_queue()
James E. Blaircdccd972013-07-01 12:10:22 -0700713
James E. Blair263fba92013-02-27 13:07:19 -0800714 # Give result events priority -- they let us stop builds,
Paul Belanger174a8272017-03-14 13:20:10 -0400715 # whereas trigger events cause us to execute builds.
James E. Blaira84f0e42014-02-06 07:09:22 -0800716 while not self.result_event_queue.empty():
James E. Blairee743612012-05-29 14:49:32 -0700717 self.process_result_queue()
James E. Blaira84f0e42014-02-06 07:09:22 -0800718
719 if not self._pause:
720 while not self.trigger_event_queue.empty():
James E. Blair263fba92013-02-27 13:07:19 -0800721 self.process_event_queue()
James E. Blaire9d45c32012-05-31 09:56:45 -0700722
James E. Blair5d5bc2b2012-07-06 10:24:01 -0700723 if self._pause and self._areAllBuildsComplete():
724 self._doPauseEvent()
James E. Blaire9d45c32012-05-31 09:56:45 -0700725
James E. Blair59fdbac2015-12-07 17:08:06 -0800726 for tenant in self.abide.tenants.values():
727 for pipeline in tenant.layout.pipelines.values():
728 while pipeline.manager.processQueue():
729 pass
James E. Blair0e933c52013-07-11 10:18:52 -0700730
James E. Blaira84f0e42014-02-06 07:09:22 -0800731 except Exception:
James E. Blairee743612012-05-29 14:49:32 -0700732 self.log.exception("Exception in run handler:")
James E. Blaira84f0e42014-02-06 07:09:22 -0800733 # There may still be more events to process
734 self.wake_event.set()
735 finally:
736 self.run_handler_lock.release()
James E. Blairee743612012-05-29 14:49:32 -0700737
Joshua Hesketh4bd7da32016-02-17 20:58:47 +1100738 def maintainConnectionCache(self):
James E. Blair25796c22017-09-08 09:34:37 -0700739 # TODOv3(jeblair): update for tenants
James E. Blair0e933c52013-07-11 10:18:52 -0700740 relevant = set()
James E. Blair59fdbac2015-12-07 17:08:06 -0800741 for tenant in self.abide.tenants.values():
742 for pipeline in tenant.layout.pipelines.values():
Joshua Heskethdc7820c2016-03-11 13:14:28 +1100743 self.log.debug("Gather relevant cache items for: %s" %
James E. Blair59fdbac2015-12-07 17:08:06 -0800744 pipeline)
Joshua Heskethdc7820c2016-03-11 13:14:28 +1100745
James E. Blair59fdbac2015-12-07 17:08:06 -0800746 for item in pipeline.getAllItems():
747 relevant.add(item.change)
748 relevant.update(item.change.getRelatedChanges())
James E. Blair25796c22017-09-08 09:34:37 -0700749 for connection in self.connections.values():
Joshua Hesketh4bd7da32016-02-17 20:58:47 +1100750 connection.maintainCache(relevant)
751 self.log.debug(
752 "End maintain connection cache for: %s" % connection)
753 self.log.debug("Connection cache size: %s" % len(relevant))
James E. Blair0e933c52013-07-11 10:18:52 -0700754
James E. Blairee743612012-05-29 14:49:32 -0700755 def process_event_queue(self):
756 self.log.debug("Fetching trigger event")
757 event = self.trigger_event_queue.get()
758 self.log.debug("Processing trigger event %s" % event)
James E. Blaira84f0e42014-02-06 07:09:22 -0800759 try:
James E. Blairaa30de42017-04-25 10:56:59 -0700760 full_project_name = ('/'.join([event.project_hostname,
761 event.project_name]))
James E. Blair59fdbac2015-12-07 17:08:06 -0800762 for tenant in self.abide.tenants.values():
James E. Blairaa30de42017-04-25 10:56:59 -0700763 (trusted, project) = tenant.getProject(full_project_name)
764 if project is None:
765 continue
766 try:
767 change = project.source.getChange(event)
768 except exceptions.ChangeNotFound as e:
769 self.log.debug("Unable to get change %s from "
770 "source %s",
771 e.change, project.source)
772 continue
James E. Blair72facdc2017-08-17 10:29:12 -0700773 if ((event.branch_updated and
774 hasattr(change, 'files') and
775 change.updatesConfig()) or
776 event.branch_created or
777 event.branch_deleted):
778 # The change that just landed updates the config
779 # or a branch was just created or deleted. Clear
780 # out cached data for this project and perform a
781 # reconfiguration.
James E. Blaira615c362017-10-02 17:34:42 -0700782 self.reconfigureTenant(tenant, change.project)
James E. Blair59fdbac2015-12-07 17:08:06 -0800783 for pipeline in tenant.layout.pipelines.values():
Jan Hruban324ca5b2015-11-05 19:28:54 +0100784 if event.isPatchsetCreated():
James E. Blair59fdbac2015-12-07 17:08:06 -0800785 pipeline.manager.removeOldVersionsOfChange(change)
Jan Hruban324ca5b2015-11-05 19:28:54 +0100786 elif event.isChangeAbandoned():
James E. Blair59fdbac2015-12-07 17:08:06 -0800787 pipeline.manager.removeAbandonedChange(change)
788 if pipeline.manager.eventMatches(event, change):
James E. Blair59fdbac2015-12-07 17:08:06 -0800789 pipeline.manager.addChange(change)
James E. Blaira84f0e42014-02-06 07:09:22 -0800790 finally:
James E. Blairff791972013-01-09 11:45:43 -0800791 self.trigger_event_queue.task_done()
James E. Blair1e8dd892012-05-30 09:15:05 -0700792
James E. Blair468c8512013-12-06 13:27:19 -0800793 def process_management_queue(self):
794 self.log.debug("Fetching management event")
795 event = self.management_event_queue.get()
796 self.log.debug("Processing management event %s" % event)
James E. Blair36658cf2013-12-06 17:53:48 -0800797 try:
798 if isinstance(event, ReconfigureEvent):
799 self._doReconfigureEvent(event)
James E. Blair21603e62017-02-20 16:23:05 -0500800 elif isinstance(event, TenantReconfigureEvent):
James E. Blair646322f2017-01-27 15:50:34 -0800801 self._doTenantReconfigureEvent(event)
James E. Blair36658cf2013-12-06 17:53:48 -0800802 elif isinstance(event, PromoteEvent):
803 self._doPromoteEvent(event)
James E. Blaird27a96d2014-07-10 13:25:13 -0700804 elif isinstance(event, EnqueueEvent):
805 self._doEnqueueEvent(event.trigger_event)
James E. Blair36658cf2013-12-06 17:53:48 -0800806 else:
807 self.log.error("Unable to handle event %s" % event)
808 event.done()
Morgan Fainberg1b9bd782016-05-30 14:03:30 -0700809 except Exception:
James E. Blair59424ea2017-07-11 09:52:58 -0700810 self.log.exception("Exception in management event:")
Morgan Fainberg1b9bd782016-05-30 14:03:30 -0700811 event.exception(sys.exc_info())
James E. Blair468c8512013-12-06 13:27:19 -0800812 self.management_event_queue.task_done()
813
James E. Blairee743612012-05-29 14:49:32 -0700814 def process_result_queue(self):
815 self.log.debug("Fetching result event")
James E. Blaira84f0e42014-02-06 07:09:22 -0800816 event = self.result_event_queue.get()
817 self.log.debug("Processing result event %s" % event)
818 try:
819 if isinstance(event, BuildStartedEvent):
820 self._doBuildStartedEvent(event)
821 elif isinstance(event, BuildCompletedEvent):
822 self._doBuildCompletedEvent(event)
James E. Blair4076e2b2014-01-28 12:42:20 -0800823 elif isinstance(event, MergeCompletedEvent):
824 self._doMergeCompletedEvent(event)
James E. Blair8d692392016-04-08 17:47:58 -0700825 elif isinstance(event, NodesProvisionedEvent):
826 self._doNodesProvisionedEvent(event)
James E. Blaira84f0e42014-02-06 07:09:22 -0800827 else:
828 self.log.error("Unable to handle event %s" % event)
829 finally:
830 self.result_event_queue.task_done()
831
832 def _doBuildStartedEvent(self, event):
James E. Blair4076e2b2014-01-28 12:42:20 -0800833 build = event.build
834 if build.build_set is not build.build_set.item.current_build_set:
835 self.log.warning("Build %s is not in the current build set" %
836 (build,))
837 return
838 pipeline = build.build_set.item.pipeline
839 if not pipeline:
840 self.log.warning("Build %s is not associated with a pipeline" %
841 (build,))
842 return
James E. Blairce8a2132016-05-19 15:21:52 -0700843 try:
844 build.estimated_time = float(self.time_database.getEstimatedTime(
James E. Blairae0f23c2017-09-13 10:55:15 -0600845 build))
James E. Blairce8a2132016-05-19 15:21:52 -0700846 except Exception:
847 self.log.exception("Exception estimating build time:")
James E. Blair4076e2b2014-01-28 12:42:20 -0800848 pipeline.manager.onBuildStarted(event.build)
James E. Blaira84f0e42014-02-06 07:09:22 -0800849
850 def _doBuildCompletedEvent(self, event):
James E. Blair4076e2b2014-01-28 12:42:20 -0800851 build = event.build
James E. Blaire18d4602017-01-05 11:17:28 -0800852
853 # Regardless of any other conditions which might cause us not
854 # to pass this on to the pipeline manager, make sure we return
855 # the nodes to nodepool.
856 try:
857 nodeset = build.build_set.getJobNodeSet(build.job.name)
David Shrewsburyffab07a2017-07-24 12:45:07 -0400858 autohold_key = (build.pipeline.layout.tenant.name,
859 build.build_set.item.change.project.canonical_name,
860 build.job.name)
861
862 try:
863 self.nodepool.holdNodeSet(nodeset, autohold_key)
864 except Exception:
865 self.log.exception("Unable to process autohold for %s",
866 autohold_key)
867
James E. Blair1511bc32017-01-18 09:25:31 -0800868 self.nodepool.returnNodeSet(nodeset)
James E. Blaire18d4602017-01-05 11:17:28 -0800869 except Exception:
870 self.log.exception("Unable to return nodeset %s" % (nodeset,))
871
James E. Blair4076e2b2014-01-28 12:42:20 -0800872 if build.build_set is not build.build_set.item.current_build_set:
James E. Blaire18d4602017-01-05 11:17:28 -0800873 self.log.debug("Build %s is not in the current build set" %
874 (build,))
James E. Blair4076e2b2014-01-28 12:42:20 -0800875 return
876 pipeline = build.build_set.item.pipeline
877 if not pipeline:
878 self.log.warning("Build %s is not associated with a pipeline" %
879 (build,))
880 return
James E. Blairce8a2132016-05-19 15:21:52 -0700881 if build.end_time and build.start_time and build.result:
882 duration = build.end_time - build.start_time
Paul Belanger87e4ab02016-06-08 14:17:20 -0400883 try:
James E. Blairae0f23c2017-09-13 10:55:15 -0600884 self.time_database.update(build, duration, build.result)
Paul Belanger87e4ab02016-06-08 14:17:20 -0400885 except Exception:
886 self.log.exception("Exception recording build time:")
James E. Blair4076e2b2014-01-28 12:42:20 -0800887 pipeline.manager.onBuildCompleted(event.build)
888
889 def _doMergeCompletedEvent(self, event):
890 build_set = event.build_set
891 if build_set is not build_set.item.current_build_set:
892 self.log.warning("Build set %s is not current" % (build_set,))
893 return
894 pipeline = build_set.item.pipeline
895 if not pipeline:
896 self.log.warning("Build set %s is not associated with a pipeline" %
897 (build_set,))
898 return
899 pipeline.manager.onMergeCompleted(event)
James E. Blairee743612012-05-29 14:49:32 -0700900
James E. Blair8d692392016-04-08 17:47:58 -0700901 def _doNodesProvisionedEvent(self, event):
902 request = event.request
903 build_set = request.build_set
James E. Blaira38c28e2017-01-04 10:33:20 -0800904
James E. Blair6ab79e02017-01-06 10:10:17 -0800905 self.nodepool.acceptNodes(request)
James E. Blaircbbce0d2017-05-19 07:28:29 -0700906 if request.canceled:
907 return
James E. Blaira38c28e2017-01-04 10:33:20 -0800908
James E. Blair8d692392016-04-08 17:47:58 -0700909 if build_set is not build_set.item.current_build_set:
910 self.log.warning("Build set %s is not current" % (build_set,))
James E. Blair6ab79e02017-01-06 10:10:17 -0800911 if request.fulfilled:
James E. Blair1511bc32017-01-18 09:25:31 -0800912 self.nodepool.returnNodeSet(request.nodeset)
James E. Blair8d692392016-04-08 17:47:58 -0700913 return
914 pipeline = build_set.item.pipeline
915 if not pipeline:
916 self.log.warning("Build set %s is not associated with a pipeline" %
917 (build_set,))
James E. Blair6ab79e02017-01-06 10:10:17 -0800918 if request.fulfilled:
James E. Blair1511bc32017-01-18 09:25:31 -0800919 self.nodepool.returnNodeSet(request.nodeset)
James E. Blair8d692392016-04-08 17:47:58 -0700920 return
921 pipeline.manager.onNodesProvisioned(event)
922
Paul Belanger6349d152016-10-30 16:21:17 -0400923 def formatStatusJSON(self, tenant_name):
James E. Blair59fdbac2015-12-07 17:08:06 -0800924 # TODOv3(jeblair): use tenants
James E. Blair8dbd56a2012-12-22 10:55:10 -0800925 data = {}
Sergey Lukjanov5ba961b2013-12-27 01:21:04 +0400926
927 data['zuul_version'] = self.zuul_version
Tobias Henkelb4407fc2017-07-07 13:52:56 +0200928 websocket_url = get_default(self.config, 'web', 'websocket_url', None)
Sergey Lukjanov5ba961b2013-12-27 01:21:04 +0400929
James E. Blair8dbd56a2012-12-22 10:55:10 -0800930 if self._pause:
931 ret = '<p><b>Queue only mode:</b> preparing to '
James E. Blair8dbd56a2012-12-22 10:55:10 -0800932 if self._exit:
933 ret += 'exit'
934 ret += ', queue length: %s' % self.trigger_event_queue.qsize()
935 ret += '</p>'
936 data['message'] = ret
937
James E. Blairfb682cc2013-02-26 15:23:27 -0800938 data['trigger_event_queue'] = {}
939 data['trigger_event_queue']['length'] = \
940 self.trigger_event_queue.qsize()
941 data['result_event_queue'] = {}
942 data['result_event_queue']['length'] = \
943 self.result_event_queue.qsize()
944
Sergey Lukjanov5d0438d2013-12-24 03:36:39 +0400945 if self.last_reconfigured:
946 data['last_reconfigured'] = self.last_reconfigured * 1000
947
James E. Blair8dbd56a2012-12-22 10:55:10 -0800948 pipelines = []
949 data['pipelines'] = pipelines
Paul Belanger6349d152016-10-30 16:21:17 -0400950 tenant = self.abide.tenants.get(tenant_name)
951 for pipeline in tenant.layout.pipelines.values():
Tobias Henkelb4407fc2017-07-07 13:52:56 +0200952 pipelines.append(pipeline.formatStatusJSON(websocket_url))
James E. Blair8dbd56a2012-12-22 10:55:10 -0800953 return json.dumps(data)