blob: 33b6723948d4956aefd8166512c7bec9aa6f0dde [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. Blair8dbd56a2012-12-22 10:55:10 -080018import json
James E. Blairee743612012-05-29 14:49:32 -070019import logging
Zhongyue Luo1c860d72012-07-19 11:03:56 +080020import os
James E. Blair5d5bc2b2012-07-06 10:24:01 -070021import pickle
Monty Taylorb934c1a2017-06-16 19:31:47 -050022import queue
James E. Blair8b2a1472017-02-19 15:33:55 -080023import socket
James E. Blair36658cf2013-12-06 17:53:48 -080024import sys
Zhongyue Luo1c860d72012-07-19 11:03:56 +080025import threading
James E. Blair71e94122012-12-24 17:53:08 -080026import time
James E. Blairee743612012-05-29 14:49:32 -070027
Joshua Hesketh0aa7e8b2016-07-14 00:12:25 +100028from zuul import configloader
Morgan Fainberg9c4700a2016-05-30 14:25:19 -070029from zuul import model
James E. Blair83005782015-12-11 14:46:03 -080030from zuul import exceptions
Sergey Lukjanov5ba961b2013-12-27 01:21:04 +040031from zuul import version as zuul_version
Tristan Cacqueray91601d72017-06-15 06:00:12 +000032from zuul.lib.config import get_default
James E. Blairded241e2017-10-10 13:22:40 -070033from zuul.lib.statsd import get_statsd
James E. Blair419a8672017-10-18 14:48:25 -070034import zuul.lib.queue
James E. Blairee743612012-05-29 14:49:32 -070035
James E. Blair1e8dd892012-05-30 09:15:05 -070036
James E. Blair468c8512013-12-06 13:27:19 -080037class ManagementEvent(object):
38 """An event that should be processed within the main queue run loop"""
39 def __init__(self):
40 self._wait_event = threading.Event()
Morgan Fainberg1b9bd782016-05-30 14:03:30 -070041 self._exc_info = None
James E. Blair468c8512013-12-06 13:27:19 -080042
Morgan Fainberg1b9bd782016-05-30 14:03:30 -070043 def exception(self, exc_info):
44 self._exc_info = exc_info
James E. Blair36658cf2013-12-06 17:53:48 -080045 self._wait_event.set()
46
47 def done(self):
James E. Blair468c8512013-12-06 13:27:19 -080048 self._wait_event.set()
49
50 def wait(self, timeout=None):
51 self._wait_event.wait(timeout)
Morgan Fainberg1b9bd782016-05-30 14:03:30 -070052 if self._exc_info:
Thomas Bechtold7f68ec42017-06-30 14:24:52 +020053 # sys.exc_info returns (type, value, traceback)
54 type_, exception_instance, traceback = self._exc_info
55 raise exception_instance.with_traceback(traceback)
James E. Blair468c8512013-12-06 13:27:19 -080056 return self._wait_event.is_set()
57
58
59class ReconfigureEvent(ManagementEvent):
60 """Reconfigure the scheduler. The layout will be (re-)loaded from
61 the path specified in the configuration.
62
63 :arg ConfigParser config: the new configuration
64 """
65 def __init__(self, config):
66 super(ReconfigureEvent, self).__init__()
67 self.config = config
68
69
James E. Blair646322f2017-01-27 15:50:34 -080070class TenantReconfigureEvent(ManagementEvent):
71 """Reconfigure the given tenant. The layout will be (re-)loaded from
72 the path specified in the configuration.
73
74 :arg Tenant tenant: the tenant to reconfigure
James E. Blaira615c362017-10-02 17:34:42 -070075 :arg Project project: if supplied, clear the cached configuration
76 from this project first
James E. Blair646322f2017-01-27 15:50:34 -080077 """
James E. Blaira615c362017-10-02 17:34:42 -070078 def __init__(self, tenant, project):
James E. Blair646322f2017-01-27 15:50:34 -080079 super(TenantReconfigureEvent, self).__init__()
James E. Blair419a8672017-10-18 14:48:25 -070080 self.tenant_name = tenant.name
81 self.projects = set([project])
82
83 def __ne__(self, other):
84 return not self.__eq__(other)
85
86 def __eq__(self, other):
87 if not isinstance(other, TenantReconfigureEvent):
88 return False
89 # We don't check projects because they will get combined when
90 # merged.
91 return (self.tenant_name == other.tenant_name)
92
93 def merge(self, other):
94 if self.tenant_name != other.tenant_name:
95 raise Exception("Can not merge events from different tenants")
96 self.projects |= other.projects
James E. Blair646322f2017-01-27 15:50:34 -080097
98
James E. Blair36658cf2013-12-06 17:53:48 -080099class PromoteEvent(ManagementEvent):
100 """Promote one or more changes to the head of the queue.
101
Paul Belangerbaca3132016-11-04 12:49:54 -0400102 :arg str tenant_name: the name of the tenant
James E. Blair36658cf2013-12-06 17:53:48 -0800103 :arg str pipeline_name: the name of the pipeline
104 :arg list change_ids: a list of strings of change ids in the form
105 1234,1
106 """
107
Paul Belangerbaca3132016-11-04 12:49:54 -0400108 def __init__(self, tenant_name, pipeline_name, change_ids):
James E. Blair36658cf2013-12-06 17:53:48 -0800109 super(PromoteEvent, self).__init__()
Paul Belangerbaca3132016-11-04 12:49:54 -0400110 self.tenant_name = tenant_name
James E. Blair36658cf2013-12-06 17:53:48 -0800111 self.pipeline_name = pipeline_name
112 self.change_ids = change_ids
113
114
James E. Blaird27a96d2014-07-10 13:25:13 -0700115class EnqueueEvent(ManagementEvent):
116 """Enqueue a change into a pipeline
117
118 :arg TriggerEvent trigger_event: a TriggerEvent describing the
119 trigger, pipeline, and change to enqueue
120 """
121
122 def __init__(self, trigger_event):
123 super(EnqueueEvent, self).__init__()
124 self.trigger_event = trigger_event
125
126
James E. Blaira84f0e42014-02-06 07:09:22 -0800127class ResultEvent(object):
128 """An event that needs to modify the pipeline state due to a
129 result from an external system."""
130
131 pass
132
133
134class BuildStartedEvent(ResultEvent):
135 """A build has started.
136
137 :arg Build build: The build which has started.
138 """
139
140 def __init__(self, build):
141 self.build = build
142
143
144class BuildCompletedEvent(ResultEvent):
145 """A build has completed
146
147 :arg Build build: The build which has completed.
148 """
149
150 def __init__(self, build):
151 self.build = build
152
153
James E. Blair4076e2b2014-01-28 12:42:20 -0800154class MergeCompletedEvent(ResultEvent):
155 """A remote merge operation has completed
156
157 :arg BuildSet build_set: The build_set which is ready.
James E. Blair4076e2b2014-01-28 12:42:20 -0800158 :arg bool merged: Whether the merge succeeded (changes with refs).
159 :arg bool updated: Whether the repo was updated (changes without refs).
160 :arg str commit: The SHA of the merged commit (changes with refs).
James E. Blair1960d682017-04-28 15:44:14 -0700161 :arg dict repo_state: The starting repo state before the merge.
James E. Blair4076e2b2014-01-28 12:42:20 -0800162 """
163
Tobias Henkel34ee0882017-07-31 22:26:12 +0200164 def __init__(self, build_set, merged, updated, commit,
James E. Blair1960d682017-04-28 15:44:14 -0700165 files, repo_state):
James E. Blair4076e2b2014-01-28 12:42:20 -0800166 self.build_set = build_set
James E. Blair4076e2b2014-01-28 12:42:20 -0800167 self.merged = merged
168 self.updated = updated
169 self.commit = commit
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700170 self.files = files
James E. Blair1960d682017-04-28 15:44:14 -0700171 self.repo_state = repo_state
James E. Blair4076e2b2014-01-28 12:42:20 -0800172
173
James E. Blair8d692392016-04-08 17:47:58 -0700174class NodesProvisionedEvent(ResultEvent):
175 """Nodes have been provisioned for a build_set
176
177 :arg BuildSet build_set: The build_set which has nodes.
178 :arg list of Node objects nodes: The provisioned nodes
179 """
180
181 def __init__(self, request):
182 self.request = request
David Shrewsbury94e95882017-10-04 15:26:04 -0400183 self.request_id = request.id
James E. Blair8d692392016-04-08 17:47:58 -0700184
185
Maru Newby3fe5f852015-01-13 04:22:14 +0000186def toList(item):
187 if not item:
188 return []
189 if isinstance(item, list):
190 return item
191 return [item]
192
193
James E. Blaire9d45c32012-05-31 09:56:45 -0700194class Scheduler(threading.Thread):
James E. Blaire4de4f42017-01-19 10:35:24 -0800195 """The engine of Zuul.
196
197 The Scheduler is reponsible for recieving events and dispatching
198 them to appropriate components (including pipeline managers,
Paul Belanger174a8272017-03-14 13:20:10 -0400199 mergers and executors).
James E. Blaire4de4f42017-01-19 10:35:24 -0800200
201 It runs a single threaded main loop which processes events
202 received one at a time and takes action as appropriate. Other
203 parts of Zuul may run in their own thread, but synchronization is
204 performed within the scheduler to reduce or eliminate the need for
205 locking in most circumstances.
206
207 The main daemon will have one instance of the Scheduler class
208 running which will persist for the life of the process. The
209 Scheduler instance is supplied to other Zuul components so that
210 they can submit events or otherwise communicate with other
211 components.
212
213 """
214
James E. Blairee743612012-05-29 14:49:32 -0700215 log = logging.getLogger("zuul.Scheduler")
216
James E. Blaire4d229c2016-05-25 15:25:41 -0700217 def __init__(self, config, testonly=False):
James E. Blaire9d45c32012-05-31 09:56:45 -0700218 threading.Thread.__init__(self)
James E. Blair8a6f0c22013-07-01 12:31:34 -0400219 self.daemon = True
James E. Blair8b2a1472017-02-19 15:33:55 -0800220 self.hostname = socket.gethostname()
James E. Blairee743612012-05-29 14:49:32 -0700221 self.wake_event = threading.Event()
James E. Blaircdccd972013-07-01 12:10:22 -0700222 self.layout_lock = threading.Lock()
James E. Blaira84f0e42014-02-06 07:09:22 -0800223 self.run_handler_lock = threading.Lock()
James E. Blair5d5bc2b2012-07-06 10:24:01 -0700224 self._pause = False
James E. Blair5d5bc2b2012-07-06 10:24:01 -0700225 self._exit = False
James E. Blairb0fcae42012-07-17 11:12:10 -0700226 self._stopped = False
Paul Belanger174a8272017-03-14 13:20:10 -0400227 self.executor = None
James E. Blair4076e2b2014-01-28 12:42:20 -0800228 self.merger = None
James E. Blair83005782015-12-11 14:46:03 -0800229 self.connections = None
James E. Blairded241e2017-10-10 13:22:40 -0700230 self.statsd = get_statsd(config)
James E. Blair83005782015-12-11 14:46:03 -0800231 # TODO(jeblair): fix this
Joshua Hesketh352264b2015-08-11 23:42:08 +1000232 # Despite triggers being part of the pipeline, there is one trigger set
233 # per scheduler. The pipeline handles the trigger filters but since
234 # the events are handled by the scheduler itself it needs to handle
235 # the loading of the triggers.
236 # self.triggers['connection_name'] = triggerObject
James E. Blair6c358e72013-07-29 17:06:47 -0700237 self.triggers = dict()
Joshua Hesketh352264b2015-08-11 23:42:08 +1000238 self.config = config
James E. Blairee743612012-05-29 14:49:32 -0700239
Monty Taylorb934c1a2017-06-16 19:31:47 -0500240 self.trigger_event_queue = queue.Queue()
241 self.result_event_queue = queue.Queue()
James E. Blair419a8672017-10-18 14:48:25 -0700242 self.management_event_queue = zuul.lib.queue.MergedQueue()
James E. Blair59fdbac2015-12-07 17:08:06 -0800243 self.abide = model.Abide()
James E. Blairee743612012-05-29 14:49:32 -0700244
James E. Blaire4d229c2016-05-25 15:25:41 -0700245 if not testonly:
246 time_dir = self._get_time_database_dir()
247 self.time_database = model.TimeDataBase(time_dir)
James E. Blairce8a2132016-05-19 15:21:52 -0700248
Jeremy Stanley98b38de2015-06-04 21:20:43 +0000249 self.zuul_version = zuul_version.version_info.release_string()
Sergey Lukjanov5d0438d2013-12-24 03:36:39 +0400250 self.last_reconfigured = None
Jesse Keating71a47ff2017-06-06 11:36:43 -0700251 self.tenant_last_reconfigured = {}
David Shrewsburyffab07a2017-07-24 12:45:07 -0400252 self.autohold_requests = {}
Sergey Lukjanov5ba961b2013-12-27 01:21:04 +0400253
James E. Blairb0fcae42012-07-17 11:12:10 -0700254 def stop(self):
255 self._stopped = True
Joshua Hesketh352264b2015-08-11 23:42:08 +1000256 self.stopConnections()
James E. Blairb0fcae42012-07-17 11:12:10 -0700257 self.wake_event.set()
258
Jan Hruban7083edd2015-08-21 14:00:54 +0200259 def registerConnections(self, connections, webapp, load=True):
Joshua Hesketh9a256752016-04-04 13:38:51 +1000260 # load: whether or not to trigger the onLoad for the connection. This
261 # is useful for not doing a full load during layout validation.
Joshua Hesketh352264b2015-08-11 23:42:08 +1000262 self.connections = connections
Jan Hruban7083edd2015-08-21 14:00:54 +0200263 self.connections.registerWebapp(webapp)
Joshua Hesketh0aa7e8b2016-07-14 00:12:25 +1000264 self.connections.registerScheduler(self, load)
Joshua Hesketh352264b2015-08-11 23:42:08 +1000265
266 def stopConnections(self):
James E. Blair83005782015-12-11 14:46:03 -0800267 self.connections.stop()
James E. Blair14abdf42015-12-09 16:11:53 -0800268
Paul Belanger174a8272017-03-14 13:20:10 -0400269 def setExecutor(self, executor):
270 self.executor = executor
James E. Blairee743612012-05-29 14:49:32 -0700271
James E. Blair4076e2b2014-01-28 12:42:20 -0800272 def setMerger(self, merger):
273 self.merger = merger
274
James E. Blair8d692392016-04-08 17:47:58 -0700275 def setNodepool(self, nodepool):
276 self.nodepool = nodepool
277
James E. Blairdce6cea2016-12-20 16:45:32 -0800278 def setZooKeeper(self, zk):
279 self.zk = zk
280
James E. Blairee743612012-05-29 14:49:32 -0700281 def addEvent(self, event):
James E. Blairee743612012-05-29 14:49:32 -0700282 self.trigger_event_queue.put(event)
283 self.wake_event.set()
284
James E. Blair11700c32012-07-05 17:50:05 -0700285 def onBuildStarted(self, build):
James E. Blair71e94122012-12-24 17:53:08 -0800286 build.start_time = time.time()
James E. Blaira84f0e42014-02-06 07:09:22 -0800287 event = BuildStartedEvent(build)
288 self.result_event_queue.put(event)
James E. Blair11700c32012-07-05 17:50:05 -0700289 self.wake_event.set()
290
James E. Blair196f61a2017-06-30 15:42:29 -0700291 def onBuildCompleted(self, build, result, result_data):
James E. Blair71e94122012-12-24 17:53:08 -0800292 build.end_time = time.time()
James E. Blair196f61a2017-06-30 15:42:29 -0700293 build.result_data = result_data
James E. Blairf0358662015-07-20 15:19:12 -0700294 # Note, as soon as the result is set, other threads may act
295 # upon this, even though the event hasn't been fully
296 # processed. Ensure that any other data from the event (eg,
297 # timing) is recorded before setting the result.
298 build.result = result
James E. Blair23ec1ba2013-01-04 18:06:10 -0800299 try:
James E. Blair552b54f2016-07-22 13:55:32 -0700300 if self.statsd and build.pipeline:
James E. Blair80ac1582017-10-09 07:02:40 -0700301 tenant = build.pipeline.layout.tenant
302 jobname = build.job.name.replace('.', '_').replace('/', '_')
303 hostname = (build.build_set.item.change.project.
304 canonical_hostname.replace('.', '_'))
305 projectname = (build.build_set.item.change.project.name.
306 replace('.', '_').replace('/', '_'))
307 branchname = (build.build_set.item.change.branch.
308 replace('.', '_').replace('/', '_'))
309 basekey = 'zuul.tenant.%s' % tenant.name
310 pipekey = '%s.pipeline.%s' % (basekey, build.pipeline.name)
311 # zuul.tenant.<tenant>.pipeline.<pipeline>.all_jobs
312 key = '%s.all_jobs' % pipekey
James E. Blair552b54f2016-07-22 13:55:32 -0700313 self.statsd.incr(key)
James E. Blair80ac1582017-10-09 07:02:40 -0700314 jobkey = '%s.project.%s.%s.%s.job.%s' % (
315 pipekey, hostname, projectname, branchname, jobname)
316 # zuul.tenant.<tenant>.pipeline.<pipeline>.project.
317 # <host>.<project>.<branch>.job.<job>.<result>
318 key = '%s.%s' % (jobkey, build.result)
James E. Blair23ec1ba2013-01-04 18:06:10 -0800319 if build.result in ['SUCCESS', 'FAILURE'] and build.start_time:
320 dt = int((build.end_time - build.start_time) * 1000)
James E. Blair552b54f2016-07-22 13:55:32 -0700321 self.statsd.timing(key, dt)
322 self.statsd.incr(key)
James E. Blair80ac1582017-10-09 07:02:40 -0700323 # zuul.tenant.<tenant>.pipeline.<pipeline>.project.
324 # <host>.<project>.<branch>.job.<job>.wait_time
325 key = '%s.wait_time' % jobkey
Paul Belanger174a8272017-03-14 13:20:10 -0400326 dt = int((build.start_time - build.execute_time) * 1000)
James E. Blair552b54f2016-07-22 13:55:32 -0700327 self.statsd.timing(key, dt)
James E. Blair80ac1582017-10-09 07:02:40 -0700328 except Exception:
James E. Blair23ec1ba2013-01-04 18:06:10 -0800329 self.log.exception("Exception reporting runtime stats")
James E. Blaira84f0e42014-02-06 07:09:22 -0800330 event = BuildCompletedEvent(build)
331 self.result_event_queue.put(event)
James E. Blairee743612012-05-29 14:49:32 -0700332 self.wake_event.set()
333
Tobias Henkel34ee0882017-07-31 22:26:12 +0200334 def onMergeCompleted(self, build_set, merged, updated,
James E. Blair1960d682017-04-28 15:44:14 -0700335 commit, files, repo_state):
Tobias Henkel34ee0882017-07-31 22:26:12 +0200336 event = MergeCompletedEvent(build_set, merged,
James E. Blair1960d682017-04-28 15:44:14 -0700337 updated, commit, files, repo_state)
James E. Blair4076e2b2014-01-28 12:42:20 -0800338 self.result_event_queue.put(event)
339 self.wake_event.set()
340
James E. Blair8d692392016-04-08 17:47:58 -0700341 def onNodesProvisioned(self, req):
James E. Blair8d692392016-04-08 17:47:58 -0700342 event = NodesProvisionedEvent(req)
343 self.result_event_queue.put(event)
344 self.wake_event.set()
345
James E. Blaira615c362017-10-02 17:34:42 -0700346 def reconfigureTenant(self, tenant, project):
347 self.log.debug("Submitting tenant reconfiguration event for "
348 "%s due to project %s", tenant.name, project)
349 event = TenantReconfigureEvent(tenant, project)
James E. Blair646322f2017-01-27 15:50:34 -0800350 self.management_event_queue.put(event)
351 self.wake_event.set()
352
James E. Blaire9d45c32012-05-31 09:56:45 -0700353 def reconfigure(self, config):
James E. Blaira615c362017-10-02 17:34:42 -0700354 self.log.debug("Submitting reconfiguration event")
James E. Blair468c8512013-12-06 13:27:19 -0800355 event = ReconfigureEvent(config)
356 self.management_event_queue.put(event)
James E. Blaire9d45c32012-05-31 09:56:45 -0700357 self.wake_event.set()
358 self.log.debug("Waiting for reconfiguration")
James E. Blair468c8512013-12-06 13:27:19 -0800359 event.wait()
James E. Blaire9d45c32012-05-31 09:56:45 -0700360 self.log.debug("Reconfiguration complete")
Sergey Lukjanov5d0438d2013-12-24 03:36:39 +0400361 self.last_reconfigured = int(time.time())
James E. Blair646322f2017-01-27 15:50:34 -0800362 # TODOv3(jeblair): reconfigure time should be per-tenant
James E. Blaire9d45c32012-05-31 09:56:45 -0700363
David Shrewsbury36b2adf2017-07-31 15:40:13 -0400364 def autohold(self, tenant_name, project_name, job_name, reason, count):
David Shrewsburyffab07a2017-07-24 12:45:07 -0400365 key = (tenant_name, project_name, job_name)
366 if count == 0 and key in self.autohold_requests:
367 self.log.debug("Removing autohold for %s", key)
368 del self.autohold_requests[key]
369 else:
370 self.log.debug("Autohold requested for %s", key)
David Shrewsbury36b2adf2017-07-31 15:40:13 -0400371 self.autohold_requests[key] = (count, reason)
David Shrewsburyffab07a2017-07-24 12:45:07 -0400372
Paul Belangerbaca3132016-11-04 12:49:54 -0400373 def promote(self, tenant_name, pipeline_name, change_ids):
374 event = PromoteEvent(tenant_name, pipeline_name, change_ids)
James E. Blair36658cf2013-12-06 17:53:48 -0800375 self.management_event_queue.put(event)
376 self.wake_event.set()
377 self.log.debug("Waiting for promotion")
378 event.wait()
379 self.log.debug("Promotion complete")
380
James E. Blaird27a96d2014-07-10 13:25:13 -0700381 def enqueue(self, trigger_event):
382 event = EnqueueEvent(trigger_event)
383 self.management_event_queue.put(event)
384 self.wake_event.set()
385 self.log.debug("Waiting for enqueue")
386 event.wait()
387 self.log.debug("Enqueue complete")
388
James E. Blair5d5bc2b2012-07-06 10:24:01 -0700389 def exit(self):
390 self.log.debug("Prepare to exit")
391 self._pause = True
392 self._exit = True
393 self.wake_event.set()
394 self.log.debug("Waiting for exit")
395
396 def _get_queue_pickle_file(self):
James E. Blaird1de9462017-06-23 20:53:09 +0100397 state_dir = get_default(self.config, 'scheduler', 'state_dir',
Tristan Cacqueray91601d72017-06-15 06:00:12 +0000398 '/var/lib/zuul', expand_user=True)
James E. Blair5d5bc2b2012-07-06 10:24:01 -0700399 return os.path.join(state_dir, 'queue.pickle')
400
James E. Blairce8a2132016-05-19 15:21:52 -0700401 def _get_time_database_dir(self):
James E. Blaird1de9462017-06-23 20:53:09 +0100402 state_dir = get_default(self.config, 'scheduler', 'state_dir',
Tristan Cacqueray91601d72017-06-15 06:00:12 +0000403 '/var/lib/zuul', expand_user=True)
James E. Blairce8a2132016-05-19 15:21:52 -0700404 d = os.path.join(state_dir, 'times')
405 if not os.path.exists(d):
406 os.mkdir(d)
407 return d
408
Ricardo Carrillo Cruz22994f92016-12-02 11:41:58 +0000409 def _get_project_key_dir(self):
James E. Blaird1de9462017-06-23 20:53:09 +0100410 state_dir = get_default(self.config, 'scheduler', 'state_dir',
Tristan Cacqueray91601d72017-06-15 06:00:12 +0000411 '/var/lib/zuul', expand_user=True)
Ricardo Carrillo Cruz22994f92016-12-02 11:41:58 +0000412 key_dir = os.path.join(state_dir, 'keys')
413 if not os.path.exists(key_dir):
414 os.mkdir(key_dir, 0o700)
415 st = os.stat(key_dir)
416 mode = st.st_mode & 0o777
417 if mode != 0o700:
418 raise Exception("Project key directory %s must be mode 0700; "
419 "current mode is %o" % (key_dir, mode))
420 return key_dir
421
James E. Blair5d5bc2b2012-07-06 10:24:01 -0700422 def _save_queue(self):
423 pickle_file = self._get_queue_pickle_file()
424 events = []
425 while not self.trigger_event_queue.empty():
426 events.append(self.trigger_event_queue.get())
427 self.log.debug("Queue length is %s" % len(events))
428 if events:
429 self.log.debug("Saving queue")
430 pickle.dump(events, open(pickle_file, 'wb'))
431
432 def _load_queue(self):
433 pickle_file = self._get_queue_pickle_file()
434 if os.path.exists(pickle_file):
435 self.log.debug("Loading queue")
436 events = pickle.load(open(pickle_file, 'rb'))
437 self.log.debug("Queue length is %s" % len(events))
438 for event in events:
439 self.trigger_event_queue.put(event)
440 else:
441 self.log.debug("No queue file found")
442
443 def _delete_queue(self):
444 pickle_file = self._get_queue_pickle_file()
445 if os.path.exists(pickle_file):
446 self.log.debug("Deleting saved queue")
447 os.unlink(pickle_file)
448
449 def resume(self):
450 try:
451 self._load_queue()
452 except:
453 self.log.exception("Unable to load queue")
454 try:
455 self._delete_queue()
456 except:
457 self.log.exception("Unable to delete saved queue")
458 self.log.debug("Resuming queue processing")
459 self.wake_event.set()
460
461 def _doPauseEvent(self):
462 if self._exit:
463 self.log.debug("Exiting")
464 self._save_queue()
465 os._exit(0)
James E. Blaircdccd972013-07-01 12:10:22 -0700466
James E. Blair468c8512013-12-06 13:27:19 -0800467 def _doReconfigureEvent(self, event):
468 # This is called in the scheduler loop after another thread submits
469 # a request
James E. Blaircdccd972013-07-01 12:10:22 -0700470 self.layout_lock.acquire()
James E. Blair468c8512013-12-06 13:27:19 -0800471 self.config = event.config
James E. Blaircdccd972013-07-01 12:10:22 -0700472 try:
James E. Blaira615c362017-10-02 17:34:42 -0700473 self.log.debug("Full reconfiguration beginning")
James E. Blair83005782015-12-11 14:46:03 -0800474 loader = configloader.ConfigLoader()
475 abide = loader.loadConfig(
James E. Blair39840362017-06-23 20:34:02 +0100476 self.config.get('scheduler', 'tenant_config'),
Ricardo Carrillo Cruz22994f92016-12-02 11:41:58 +0000477 self._get_project_key_dir(),
James E. Blair83005782015-12-11 14:46:03 -0800478 self, self.merger, self.connections)
James E. Blair59fdbac2015-12-07 17:08:06 -0800479 for tenant in abide.tenants.values():
480 self._reconfigureTenant(tenant)
481 self.abide = abide
James E. Blaircdccd972013-07-01 12:10:22 -0700482 finally:
483 self.layout_lock.release()
James E. Blaira615c362017-10-02 17:34:42 -0700484 self.log.debug("Full reconfiguration complete")
James E. Blaire9d45c32012-05-31 09:56:45 -0700485
James E. Blair646322f2017-01-27 15:50:34 -0800486 def _doTenantReconfigureEvent(self, event):
487 # This is called in the scheduler loop after another thread submits
488 # a request
489 self.layout_lock.acquire()
490 try:
James E. Blaira615c362017-10-02 17:34:42 -0700491 self.log.debug("Tenant reconfiguration beginning")
492 # If a change landed to a project, clear out the cached
493 # config before reconfiguring.
James E. Blair419a8672017-10-18 14:48:25 -0700494 for project in event.projects:
495 project.unparsed_config = None
496 old_tenant = self.abide.tenants[event.tenant_name]
James E. Blair646322f2017-01-27 15:50:34 -0800497 loader = configloader.ConfigLoader()
498 abide = loader.reloadTenant(
James E. Blair39840362017-06-23 20:34:02 +0100499 self.config.get('scheduler', 'tenant_config'),
Ricardo Carrillo Cruz22994f92016-12-02 11:41:58 +0000500 self._get_project_key_dir(),
James E. Blair646322f2017-01-27 15:50:34 -0800501 self, self.merger, self.connections,
James E. Blair419a8672017-10-18 14:48:25 -0700502 self.abide, old_tenant)
503 tenant = abide.tenants[event.tenant_name]
James E. Blair646322f2017-01-27 15:50:34 -0800504 self._reconfigureTenant(tenant)
505 self.abide = abide
506 finally:
507 self.layout_lock.release()
James E. Blaira615c362017-10-02 17:34:42 -0700508 self.log.debug("Tenant reconfiguration complete")
James E. Blair646322f2017-01-27 15:50:34 -0800509
James E. Blairaa30de42017-04-25 10:56:59 -0700510 def _reenqueueGetProject(self, tenant, item):
511 project = item.change.project
James E. Blair6053de42017-04-05 11:27:11 -0700512 # Attempt to get the same project as the one passed in. If
513 # the project is now found on a different connection, return
514 # the new version of the project. If it is no longer
515 # available (due to a connection being removed), return None.
James E. Blairaa30de42017-04-25 10:56:59 -0700516 (trusted, new_project) = tenant.getProject(project.canonical_name)
James E. Blair6053de42017-04-05 11:27:11 -0700517 if new_project:
518 return new_project
James E. Blairaa30de42017-04-25 10:56:59 -0700519 # If this is a non-live item we may be looking at a
520 # "foreign" project, ie, one which is not defined in the
521 # config but is constructed ad-hoc to satisfy a
522 # cross-repo-dependency. Find the corresponding live item
523 # and use its source.
524 child = item
525 while child and not child.live:
526 # This assumes that the queue does not branch behind this
527 # item, which is currently true for non-live items; if
528 # that changes, this traversal will need to be more
529 # complex.
530 if child.items_behind:
531 child = child.items_behind[0]
532 else:
533 child = None
534 if child is item:
535 return None
536 if child and child.live:
537 (child_trusted, child_project) = tenant.getProject(
538 child.change.project.canonical_name)
539 if child_project:
540 source = child_project.source
541 new_project = source.getProject(project.name)
542 return new_project
543 return None
James E. Blair6053de42017-04-05 11:27:11 -0700544
James E. Blair552b54f2016-07-22 13:55:32 -0700545 def _reenqueueTenant(self, old_tenant, tenant):
James E. Blair59fdbac2015-12-07 17:08:06 -0800546 for name, new_pipeline in tenant.layout.pipelines.items():
547 old_pipeline = old_tenant.layout.pipelines.get(name)
548 if not old_pipeline:
549 self.log.warning("No old pipeline matching %s found "
550 "when reconfiguring" % name)
551 continue
552 self.log.debug("Re-enqueueing changes for pipeline %s" % name)
553 items_to_remove = []
554 builds_to_cancel = []
555 last_head = None
556 for shared_queue in old_pipeline.queues:
557 for item in shared_queue.queue:
558 if not item.item_ahead:
559 last_head = item
James E. Blair59fdbac2015-12-07 17:08:06 -0800560 item.pipeline = None
561 item.queue = None
James E. Blair6053de42017-04-05 11:27:11 -0700562 item.change.project = self._reenqueueGetProject(
James E. Blairaa30de42017-04-25 10:56:59 -0700563 tenant, item)
564 item.item_ahead = None
565 item.items_behind = []
James E. Blair027ba992017-09-20 13:48:32 -0700566 reenqueued = False
567 if item.change.project:
568 try:
569 reenqueued = new_pipeline.manager.reEnqueueItem(
570 item, last_head)
571 except Exception:
572 self.log.exception(
573 "Exception while re-enqueing item %s",
574 item)
575 if reenqueued:
James E. Blair3b5ff3b2016-07-21 10:08:24 -0700576 for build in item.current_build_set.getBuilds():
Fredrik Medleyf8aec832015-09-28 13:40:20 +0200577 new_job = item.getJob(build.job.name)
578 if new_job:
579 build.job = new_job
James E. Blair3b5ff3b2016-07-21 10:08:24 -0700580 else:
581 item.removeBuild(build)
582 builds_to_cancel.append(build)
583 else:
James E. Blair59fdbac2015-12-07 17:08:06 -0800584 items_to_remove.append(item)
585 for item in items_to_remove:
James E. Blairb5a8f0b2017-07-07 17:01:18 -0700586 self.log.warning(
587 "Removing item %s during reconfiguration" % (item,))
James E. Blair59fdbac2015-12-07 17:08:06 -0800588 for build in item.current_build_set.getBuilds():
589 builds_to_cancel.append(build)
590 for build in builds_to_cancel:
591 self.log.warning(
592 "Canceling build %s during reconfiguration" % (build,))
593 try:
Paul Belanger174a8272017-03-14 13:20:10 -0400594 self.executor.cancel(build)
James E. Blair59fdbac2015-12-07 17:08:06 -0800595 except Exception:
596 self.log.exception(
597 "Exception while canceling build %s "
Tobias Henkel9a0e1942017-03-20 16:16:02 +0100598 "for change %s" % (build, build.build_set.item.change))
Tobias Henkelfb91a492017-02-15 07:29:43 +0100599 finally:
Tobias Henkel9a0e1942017-03-20 16:16:02 +0100600 tenant.semaphore_handler.release(
601 build.build_set.item, build.job)
James E. Blair552b54f2016-07-22 13:55:32 -0700602
603 def _reconfigureTenant(self, tenant):
604 # This is called from _doReconfigureEvent while holding the
605 # layout lock
606 old_tenant = self.abide.tenants.get(tenant.name)
Tobias Henkel9a0e1942017-03-20 16:16:02 +0100607
James E. Blair552b54f2016-07-22 13:55:32 -0700608 if old_tenant:
Tobias Henkel9a0e1942017-03-20 16:16:02 +0100609 # Copy over semaphore handler so we don't loose the currently
610 # held semaphores.
611 tenant.semaphore_handler = old_tenant.semaphore_handler
612
James E. Blair552b54f2016-07-22 13:55:32 -0700613 self._reenqueueTenant(old_tenant, tenant)
Tobias Henkel9a0e1942017-03-20 16:16:02 +0100614
James E. Blairb0a95ab2017-10-18 09:39:18 -0700615 # TODOv3(jeblair): update for tenants
616 # self.maintainConnectionCache()
James E. Blaire511d2f2016-12-08 15:22:26 -0800617 self.connections.reconfigureDrivers(tenant)
Tobias Henkel9a0e1942017-03-20 16:16:02 +0100618
James E. Blaire511d2f2016-12-08 15:22:26 -0800619 # TODOv3(jeblair): remove postconfig calls?
James E. Blair59fdbac2015-12-07 17:08:06 -0800620 for pipeline in tenant.layout.pipelines.values():
James E. Blair552b54f2016-07-22 13:55:32 -0700621 for trigger in pipeline.triggers:
622 trigger.postConfig(pipeline)
James E. Blair83005782015-12-11 14:46:03 -0800623 for reporter in pipeline.actions:
624 reporter.postConfig()
Jesse Keating71a47ff2017-06-06 11:36:43 -0700625 self.tenant_last_reconfigured[tenant.name] = int(time.time())
James E. Blair552b54f2016-07-22 13:55:32 -0700626 if self.statsd:
James E. Blair59fdbac2015-12-07 17:08:06 -0800627 try:
James E. Blair552b54f2016-07-22 13:55:32 -0700628 for pipeline in tenant.layout.pipelines.values():
James E. Blair59fdbac2015-12-07 17:08:06 -0800629 items = len(pipeline.getAllItems())
630 # stats.gauges.zuul.pipeline.NAME.current_changes
631 key = 'zuul.pipeline.%s' % pipeline.name
James E. Blair552b54f2016-07-22 13:55:32 -0700632 self.statsd.gauge(key + '.current_changes', items)
James E. Blair59fdbac2015-12-07 17:08:06 -0800633 except Exception:
634 self.log.exception("Exception reporting initial "
635 "pipeline stats:")
636
James E. Blair36658cf2013-12-06 17:53:48 -0800637 def _doPromoteEvent(self, event):
Paul Belangerbaca3132016-11-04 12:49:54 -0400638 tenant = self.abide.tenants.get(event.tenant_name)
639 pipeline = tenant.layout.pipelines[event.pipeline_name]
James E. Blair36658cf2013-12-06 17:53:48 -0800640 change_ids = [c.split(',') for c in event.change_ids]
641 items_to_enqueue = []
642 change_queue = None
643 for shared_queue in pipeline.queues:
644 if change_queue:
645 break
646 for item in shared_queue.queue:
647 if (item.change.number == change_ids[0][0] and
Joshua Hesketh29d99b72014-08-19 16:27:42 +1000648 item.change.patchset == change_ids[0][1]):
James E. Blair36658cf2013-12-06 17:53:48 -0800649 change_queue = shared_queue
650 break
651 if not change_queue:
652 raise Exception("Unable to find shared change queue for %s" %
653 event.change_ids[0])
654 for number, patchset in change_ids:
655 found = False
656 for item in change_queue.queue:
657 if (item.change.number == number and
Joshua Hesketh29d99b72014-08-19 16:27:42 +1000658 item.change.patchset == patchset):
James E. Blair36658cf2013-12-06 17:53:48 -0800659 found = True
660 items_to_enqueue.append(item)
661 break
662 if not found:
663 raise Exception("Unable to find %s,%s in queue %s" %
664 (number, patchset, change_queue))
665 for item in change_queue.queue[:]:
666 if item not in items_to_enqueue:
667 items_to_enqueue.append(item)
668 pipeline.manager.cancelJobs(item)
669 pipeline.manager.dequeueItem(item)
670 for item in items_to_enqueue:
Sean Daguef39b9ca2014-01-10 21:34:35 -0500671 pipeline.manager.addChange(
672 item.change,
673 enqueue_time=item.enqueue_time,
James E. Blairf9ab8842014-07-10 13:12:07 -0700674 quiet=True,
675 ignore_requirements=True)
James E. Blair36658cf2013-12-06 17:53:48 -0800676
James E. Blaird27a96d2014-07-10 13:25:13 -0700677 def _doEnqueueEvent(self, event):
Paul Belangerbaca3132016-11-04 12:49:54 -0400678 tenant = self.abide.tenants.get(event.tenant_name)
James E. Blair0ffa0102017-03-30 13:11:33 -0700679 (trusted, project) = tenant.getProject(event.project_name)
Paul Belangerbaca3132016-11-04 12:49:54 -0400680 pipeline = tenant.layout.pipelines[event.forced_pipeline]
James E. Blair6053de42017-04-05 11:27:11 -0700681 change = project.source.getChange(event, project)
James E. Blaird27a96d2014-07-10 13:25:13 -0700682 self.log.debug("Event %s for change %s was directly assigned "
683 "to pipeline %s" % (event, change, self))
James E. Blaird27a96d2014-07-10 13:25:13 -0700684 pipeline.manager.addChange(change, ignore_requirements=True)
685
James E. Blaire9d45c32012-05-31 09:56:45 -0700686 def _areAllBuildsComplete(self):
687 self.log.debug("Checking if all builds are complete")
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700688 if self.merger.areMergesOutstanding():
689 self.log.debug("Waiting on merger")
690 return False
James E. Blaire9d45c32012-05-31 09:56:45 -0700691 waiting = False
Paul Belangerdebd7a72016-11-11 19:56:15 -0500692 for tenant in self.abide.tenants.values():
693 for pipeline in tenant.layout.pipelines.values():
694 for item in pipeline.getAllItems():
695 for build in item.current_build_set.getBuilds():
696 if build.result is None:
697 self.log.debug("%s waiting on %s" %
698 (pipeline.manager, build))
699 waiting = True
James E. Blaire9d45c32012-05-31 09:56:45 -0700700 if not waiting:
701 self.log.debug("All builds are complete")
702 return True
James E. Blaire9d45c32012-05-31 09:56:45 -0700703 return False
704
James E. Blairee743612012-05-29 14:49:32 -0700705 def run(self):
James E. Blair552b54f2016-07-22 13:55:32 -0700706 if self.statsd:
James E. Blair71e94122012-12-24 17:53:08 -0800707 self.log.debug("Statsd enabled")
708 else:
709 self.log.debug("Statsd disabled because python statsd "
710 "package not found")
James E. Blairee743612012-05-29 14:49:32 -0700711 while True:
712 self.log.debug("Run handler sleeping")
713 self.wake_event.wait()
714 self.wake_event.clear()
James E. Blairb0fcae42012-07-17 11:12:10 -0700715 if self._stopped:
James E. Blair4076e2b2014-01-28 12:42:20 -0800716 self.log.debug("Run handler stopping")
James E. Blairb0fcae42012-07-17 11:12:10 -0700717 return
James E. Blairee743612012-05-29 14:49:32 -0700718 self.log.debug("Run handler awake")
James E. Blaira84f0e42014-02-06 07:09:22 -0800719 self.run_handler_lock.acquire()
James E. Blairee743612012-05-29 14:49:32 -0700720 try:
James E. Blaira84f0e42014-02-06 07:09:22 -0800721 while not self.management_event_queue.empty():
James E. Blair468c8512013-12-06 13:27:19 -0800722 self.process_management_queue()
James E. Blaircdccd972013-07-01 12:10:22 -0700723
James E. Blair263fba92013-02-27 13:07:19 -0800724 # Give result events priority -- they let us stop builds,
Paul Belanger174a8272017-03-14 13:20:10 -0400725 # whereas trigger events cause us to execute builds.
James E. Blaira84f0e42014-02-06 07:09:22 -0800726 while not self.result_event_queue.empty():
James E. Blairee743612012-05-29 14:49:32 -0700727 self.process_result_queue()
James E. Blaira84f0e42014-02-06 07:09:22 -0800728
729 if not self._pause:
730 while not self.trigger_event_queue.empty():
James E. Blair263fba92013-02-27 13:07:19 -0800731 self.process_event_queue()
James E. Blaire9d45c32012-05-31 09:56:45 -0700732
James E. Blair5d5bc2b2012-07-06 10:24:01 -0700733 if self._pause and self._areAllBuildsComplete():
734 self._doPauseEvent()
James E. Blaire9d45c32012-05-31 09:56:45 -0700735
James E. Blair59fdbac2015-12-07 17:08:06 -0800736 for tenant in self.abide.tenants.values():
737 for pipeline in tenant.layout.pipelines.values():
738 while pipeline.manager.processQueue():
739 pass
James E. Blair0e933c52013-07-11 10:18:52 -0700740
James E. Blaira84f0e42014-02-06 07:09:22 -0800741 except Exception:
James E. Blairee743612012-05-29 14:49:32 -0700742 self.log.exception("Exception in run handler:")
James E. Blaira84f0e42014-02-06 07:09:22 -0800743 # There may still be more events to process
744 self.wake_event.set()
745 finally:
746 self.run_handler_lock.release()
James E. Blairee743612012-05-29 14:49:32 -0700747
James E. Blairb0a95ab2017-10-18 09:39:18 -0700748 def maintainConnectionCache(self):
749 # TODOv3(jeblair): update for tenants
750 relevant = set()
751 for tenant in self.abide.tenants.values():
752 for pipeline in tenant.layout.pipelines.values():
753 self.log.debug("Gather relevant cache items for: %s" %
754 pipeline)
755
756 for item in pipeline.getAllItems():
757 relevant.add(item.change)
758 relevant.update(item.change.getRelatedChanges())
759 for connection in self.connections.values():
760 connection.maintainCache(relevant)
761 self.log.debug(
762 "End maintain connection cache for: %s" % connection)
763 self.log.debug("Connection cache size: %s" % len(relevant))
764
James E. Blairee743612012-05-29 14:49:32 -0700765 def process_event_queue(self):
766 self.log.debug("Fetching trigger event")
767 event = self.trigger_event_queue.get()
768 self.log.debug("Processing trigger event %s" % event)
James E. Blaira84f0e42014-02-06 07:09:22 -0800769 try:
James E. Blairaa30de42017-04-25 10:56:59 -0700770 full_project_name = ('/'.join([event.project_hostname,
771 event.project_name]))
James E. Blair59fdbac2015-12-07 17:08:06 -0800772 for tenant in self.abide.tenants.values():
James E. Blairaa30de42017-04-25 10:56:59 -0700773 (trusted, project) = tenant.getProject(full_project_name)
774 if project is None:
775 continue
776 try:
777 change = project.source.getChange(event)
778 except exceptions.ChangeNotFound as e:
779 self.log.debug("Unable to get change %s from "
780 "source %s",
781 e.change, project.source)
782 continue
James E. Blair72facdc2017-08-17 10:29:12 -0700783 if ((event.branch_updated and
784 hasattr(change, 'files') and
785 change.updatesConfig()) or
786 event.branch_created or
787 event.branch_deleted):
788 # The change that just landed updates the config
789 # or a branch was just created or deleted. Clear
790 # out cached data for this project and perform a
791 # reconfiguration.
James E. Blaira615c362017-10-02 17:34:42 -0700792 self.reconfigureTenant(tenant, change.project)
James E. Blair59fdbac2015-12-07 17:08:06 -0800793 for pipeline in tenant.layout.pipelines.values():
Jan Hruban324ca5b2015-11-05 19:28:54 +0100794 if event.isPatchsetCreated():
James E. Blair59fdbac2015-12-07 17:08:06 -0800795 pipeline.manager.removeOldVersionsOfChange(change)
Jan Hruban324ca5b2015-11-05 19:28:54 +0100796 elif event.isChangeAbandoned():
James E. Blair59fdbac2015-12-07 17:08:06 -0800797 pipeline.manager.removeAbandonedChange(change)
798 if pipeline.manager.eventMatches(event, change):
James E. Blair59fdbac2015-12-07 17:08:06 -0800799 pipeline.manager.addChange(change)
James E. Blaira84f0e42014-02-06 07:09:22 -0800800 finally:
James E. Blairff791972013-01-09 11:45:43 -0800801 self.trigger_event_queue.task_done()
James E. Blair1e8dd892012-05-30 09:15:05 -0700802
James E. Blair468c8512013-12-06 13:27:19 -0800803 def process_management_queue(self):
804 self.log.debug("Fetching management event")
805 event = self.management_event_queue.get()
806 self.log.debug("Processing management event %s" % event)
James E. Blair36658cf2013-12-06 17:53:48 -0800807 try:
808 if isinstance(event, ReconfigureEvent):
809 self._doReconfigureEvent(event)
James E. Blair21603e62017-02-20 16:23:05 -0500810 elif isinstance(event, TenantReconfigureEvent):
James E. Blair646322f2017-01-27 15:50:34 -0800811 self._doTenantReconfigureEvent(event)
James E. Blair36658cf2013-12-06 17:53:48 -0800812 elif isinstance(event, PromoteEvent):
813 self._doPromoteEvent(event)
James E. Blaird27a96d2014-07-10 13:25:13 -0700814 elif isinstance(event, EnqueueEvent):
815 self._doEnqueueEvent(event.trigger_event)
James E. Blair36658cf2013-12-06 17:53:48 -0800816 else:
817 self.log.error("Unable to handle event %s" % event)
818 event.done()
Morgan Fainberg1b9bd782016-05-30 14:03:30 -0700819 except Exception:
James E. Blair59424ea2017-07-11 09:52:58 -0700820 self.log.exception("Exception in management event:")
Morgan Fainberg1b9bd782016-05-30 14:03:30 -0700821 event.exception(sys.exc_info())
James E. Blair468c8512013-12-06 13:27:19 -0800822 self.management_event_queue.task_done()
823
James E. Blairee743612012-05-29 14:49:32 -0700824 def process_result_queue(self):
825 self.log.debug("Fetching result event")
James E. Blaira84f0e42014-02-06 07:09:22 -0800826 event = self.result_event_queue.get()
827 self.log.debug("Processing result event %s" % event)
828 try:
829 if isinstance(event, BuildStartedEvent):
830 self._doBuildStartedEvent(event)
831 elif isinstance(event, BuildCompletedEvent):
832 self._doBuildCompletedEvent(event)
James E. Blair4076e2b2014-01-28 12:42:20 -0800833 elif isinstance(event, MergeCompletedEvent):
834 self._doMergeCompletedEvent(event)
James E. Blair8d692392016-04-08 17:47:58 -0700835 elif isinstance(event, NodesProvisionedEvent):
836 self._doNodesProvisionedEvent(event)
James E. Blaira84f0e42014-02-06 07:09:22 -0800837 else:
838 self.log.error("Unable to handle event %s" % event)
839 finally:
840 self.result_event_queue.task_done()
841
842 def _doBuildStartedEvent(self, event):
James E. Blair4076e2b2014-01-28 12:42:20 -0800843 build = event.build
844 if build.build_set is not build.build_set.item.current_build_set:
845 self.log.warning("Build %s is not in the current build set" %
846 (build,))
847 return
848 pipeline = build.build_set.item.pipeline
849 if not pipeline:
850 self.log.warning("Build %s is not associated with a pipeline" %
851 (build,))
852 return
James E. Blairce8a2132016-05-19 15:21:52 -0700853 try:
854 build.estimated_time = float(self.time_database.getEstimatedTime(
James E. Blairae0f23c2017-09-13 10:55:15 -0600855 build))
James E. Blairce8a2132016-05-19 15:21:52 -0700856 except Exception:
857 self.log.exception("Exception estimating build time:")
James E. Blair4076e2b2014-01-28 12:42:20 -0800858 pipeline.manager.onBuildStarted(event.build)
James E. Blaira84f0e42014-02-06 07:09:22 -0800859
860 def _doBuildCompletedEvent(self, event):
James E. Blair4076e2b2014-01-28 12:42:20 -0800861 build = event.build
James E. Blaire18d4602017-01-05 11:17:28 -0800862
863 # Regardless of any other conditions which might cause us not
864 # to pass this on to the pipeline manager, make sure we return
865 # the nodes to nodepool.
866 try:
867 nodeset = build.build_set.getJobNodeSet(build.job.name)
David Shrewsburyffab07a2017-07-24 12:45:07 -0400868 autohold_key = (build.pipeline.layout.tenant.name,
869 build.build_set.item.change.project.canonical_name,
870 build.job.name)
871
872 try:
873 self.nodepool.holdNodeSet(nodeset, autohold_key)
874 except Exception:
David Shrewsburyf21bb282017-10-13 11:10:00 -0400875 self.log.exception("Unable to process autohold for %s:",
David Shrewsburyffab07a2017-07-24 12:45:07 -0400876 autohold_key)
David Shrewsburyf21bb282017-10-13 11:10:00 -0400877 if autohold_key in self.autohold_requests:
878 self.log.debug("Removing autohold %s due to exception",
879 autohold_key)
880 del self.autohold_requests[autohold_key]
David Shrewsburyffab07a2017-07-24 12:45:07 -0400881
James E. Blair1511bc32017-01-18 09:25:31 -0800882 self.nodepool.returnNodeSet(nodeset)
James E. Blaire18d4602017-01-05 11:17:28 -0800883 except Exception:
884 self.log.exception("Unable to return nodeset %s" % (nodeset,))
885
James E. Blair4076e2b2014-01-28 12:42:20 -0800886 if build.build_set is not build.build_set.item.current_build_set:
James E. Blaire18d4602017-01-05 11:17:28 -0800887 self.log.debug("Build %s is not in the current build set" %
888 (build,))
James E. Blair4076e2b2014-01-28 12:42:20 -0800889 return
890 pipeline = build.build_set.item.pipeline
891 if not pipeline:
892 self.log.warning("Build %s is not associated with a pipeline" %
893 (build,))
894 return
James E. Blairce8a2132016-05-19 15:21:52 -0700895 if build.end_time and build.start_time and build.result:
896 duration = build.end_time - build.start_time
Paul Belanger87e4ab02016-06-08 14:17:20 -0400897 try:
James E. Blairae0f23c2017-09-13 10:55:15 -0600898 self.time_database.update(build, duration, build.result)
Paul Belanger87e4ab02016-06-08 14:17:20 -0400899 except Exception:
900 self.log.exception("Exception recording build time:")
James E. Blair4076e2b2014-01-28 12:42:20 -0800901 pipeline.manager.onBuildCompleted(event.build)
902
903 def _doMergeCompletedEvent(self, event):
904 build_set = event.build_set
905 if build_set is not build_set.item.current_build_set:
906 self.log.warning("Build set %s is not current" % (build_set,))
907 return
908 pipeline = build_set.item.pipeline
909 if not pipeline:
910 self.log.warning("Build set %s is not associated with a pipeline" %
911 (build_set,))
912 return
913 pipeline.manager.onMergeCompleted(event)
James E. Blairee743612012-05-29 14:49:32 -0700914
James E. Blair8d692392016-04-08 17:47:58 -0700915 def _doNodesProvisionedEvent(self, event):
916 request = event.request
David Shrewsbury94e95882017-10-04 15:26:04 -0400917 request_id = event.request_id
James E. Blair8d692392016-04-08 17:47:58 -0700918 build_set = request.build_set
James E. Blaira38c28e2017-01-04 10:33:20 -0800919
David Shrewsbury94e95882017-10-04 15:26:04 -0400920 self.nodepool.acceptNodes(request, request_id)
James E. Blaircbbce0d2017-05-19 07:28:29 -0700921 if request.canceled:
922 return
James E. Blaira38c28e2017-01-04 10:33:20 -0800923
James E. Blair8d692392016-04-08 17:47:58 -0700924 if build_set is not build_set.item.current_build_set:
925 self.log.warning("Build set %s is not current" % (build_set,))
James E. Blair6ab79e02017-01-06 10:10:17 -0800926 if request.fulfilled:
James E. Blair1511bc32017-01-18 09:25:31 -0800927 self.nodepool.returnNodeSet(request.nodeset)
James E. Blair8d692392016-04-08 17:47:58 -0700928 return
929 pipeline = build_set.item.pipeline
930 if not pipeline:
931 self.log.warning("Build set %s is not associated with a pipeline" %
932 (build_set,))
James E. Blair6ab79e02017-01-06 10:10:17 -0800933 if request.fulfilled:
James E. Blair1511bc32017-01-18 09:25:31 -0800934 self.nodepool.returnNodeSet(request.nodeset)
James E. Blair8d692392016-04-08 17:47:58 -0700935 return
936 pipeline.manager.onNodesProvisioned(event)
937
Paul Belanger6349d152016-10-30 16:21:17 -0400938 def formatStatusJSON(self, tenant_name):
James E. Blair59fdbac2015-12-07 17:08:06 -0800939 # TODOv3(jeblair): use tenants
James E. Blair8dbd56a2012-12-22 10:55:10 -0800940 data = {}
Sergey Lukjanov5ba961b2013-12-27 01:21:04 +0400941
942 data['zuul_version'] = self.zuul_version
Tobias Henkelb4407fc2017-07-07 13:52:56 +0200943 websocket_url = get_default(self.config, 'web', 'websocket_url', None)
Sergey Lukjanov5ba961b2013-12-27 01:21:04 +0400944
James E. Blair8dbd56a2012-12-22 10:55:10 -0800945 if self._pause:
946 ret = '<p><b>Queue only mode:</b> preparing to '
James E. Blair8dbd56a2012-12-22 10:55:10 -0800947 if self._exit:
948 ret += 'exit'
949 ret += ', queue length: %s' % self.trigger_event_queue.qsize()
950 ret += '</p>'
951 data['message'] = ret
952
James E. Blairfb682cc2013-02-26 15:23:27 -0800953 data['trigger_event_queue'] = {}
954 data['trigger_event_queue']['length'] = \
955 self.trigger_event_queue.qsize()
956 data['result_event_queue'] = {}
957 data['result_event_queue']['length'] = \
958 self.result_event_queue.qsize()
James E. Blair3692b612017-10-18 13:59:41 -0700959 data['management_event_queue'] = {}
960 data['management_event_queue']['length'] = \
961 self.management_event_queue.qsize()
James E. Blairfb682cc2013-02-26 15:23:27 -0800962
Sergey Lukjanov5d0438d2013-12-24 03:36:39 +0400963 if self.last_reconfigured:
964 data['last_reconfigured'] = self.last_reconfigured * 1000
965
James E. Blair8dbd56a2012-12-22 10:55:10 -0800966 pipelines = []
967 data['pipelines'] = pipelines
Paul Belanger6349d152016-10-30 16:21:17 -0400968 tenant = self.abide.tenants.get(tenant_name)
969 for pipeline in tenant.layout.pipelines.values():
Tobias Henkelb4407fc2017-07-07 13:52:56 +0200970 pipelines.append(pipeline.formatStatusJSON(websocket_url))
James E. Blair8dbd56a2012-12-22 10:55:10 -0800971 return json.dumps(data)