blob: 0ad1ec10d6e45cb80e372754c055e2476114614a [file] [log] [blame]
Clark Boylanb640e052014-04-03 16:41:46 -07001#!/usr/bin/env python
2
3# Copyright 2012 Hewlett-Packard Development Company, L.P.
James E. Blair498059b2016-12-20 13:50:13 -08004# Copyright 2016 Red Hat, Inc.
Clark Boylanb640e052014-04-03 16:41:46 -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
Christian Berendtffba5df2014-06-07 21:30:22 +020018from six.moves import configparser as ConfigParser
Clark Boylanb640e052014-04-03 16:41:46 -070019import gc
20import hashlib
21import json
22import logging
23import os
Christian Berendt12d4d722014-06-07 21:03:45 +020024from six.moves import queue as Queue
Morgan Fainberg293f7f82016-05-30 14:01:22 -070025from six.moves import urllib
Clark Boylanb640e052014-04-03 16:41:46 -070026import random
27import re
28import select
29import shutil
Monty Taylor74fa3862016-06-02 07:39:49 +030030from six.moves import reload_module
Clark Boylan21a2c812017-04-24 15:44:55 -070031try:
32 from cStringIO import StringIO
33except Exception:
34 from six import StringIO
Clark Boylanb640e052014-04-03 16:41:46 -070035import socket
36import string
37import subprocess
James E. Blair1c236df2017-02-01 14:07:24 -080038import sys
James E. Blairf84026c2015-12-08 16:11:46 -080039import tempfile
Clark Boylanb640e052014-04-03 16:41:46 -070040import threading
Clark Boylan8208c192017-04-24 18:08:08 -070041import traceback
Clark Boylanb640e052014-04-03 16:41:46 -070042import time
Joshua Heskethd78b4482015-09-14 16:56:34 -060043import uuid
44
Clark Boylanb640e052014-04-03 16:41:46 -070045
46import git
47import gear
48import fixtures
James E. Blair498059b2016-12-20 13:50:13 -080049import kazoo.client
James E. Blairdce6cea2016-12-20 16:45:32 -080050import kazoo.exceptions
Joshua Heskethd78b4482015-09-14 16:56:34 -060051import pymysql
Clark Boylanb640e052014-04-03 16:41:46 -070052import statsd
53import testtools
James E. Blair1c236df2017-02-01 14:07:24 -080054import testtools.content
55import testtools.content_type
Clint Byrum3343e3e2016-11-15 16:05:03 -080056from git.exc import NoSuchPathError
Ricardo Carrillo Cruz22994f92016-12-02 11:41:58 +000057import yaml
Clark Boylanb640e052014-04-03 16:41:46 -070058
James E. Blaire511d2f2016-12-08 15:22:26 -080059import zuul.driver.gerrit.gerritsource as gerritsource
60import zuul.driver.gerrit.gerritconnection as gerritconnection
Gregory Haynes4fc12542015-04-22 20:38:06 -070061import zuul.driver.github.githubconnection as githubconnection
Clark Boylanb640e052014-04-03 16:41:46 -070062import zuul.scheduler
63import zuul.webapp
64import zuul.rpclistener
Paul Belanger174a8272017-03-14 13:20:10 -040065import zuul.executor.server
66import zuul.executor.client
James E. Blair83005782015-12-11 14:46:03 -080067import zuul.lib.connections
Clark Boylanb640e052014-04-03 16:41:46 -070068import zuul.merger.client
James E. Blair879dafb2015-07-17 14:04:49 -070069import zuul.merger.merger
70import zuul.merger.server
James E. Blair8d692392016-04-08 17:47:58 -070071import zuul.nodepool
James E. Blairdce6cea2016-12-20 16:45:32 -080072import zuul.zk
Clark Boylanb640e052014-04-03 16:41:46 -070073
74FIXTURE_DIR = os.path.join(os.path.dirname(__file__),
75 'fixtures')
K Jonathan Harkercc3a6f02017-02-22 19:08:06 -080076
77KEEP_TEMPDIRS = bool(os.environ.get('KEEP_TEMPDIRS', False))
Clark Boylanb640e052014-04-03 16:41:46 -070078
Clark Boylanb640e052014-04-03 16:41:46 -070079
80def repack_repo(path):
81 cmd = ['git', '--git-dir=%s/.git' % path, 'repack', '-afd']
82 output = subprocess.Popen(cmd, close_fds=True,
83 stdout=subprocess.PIPE,
84 stderr=subprocess.PIPE)
85 out = output.communicate()
86 if output.returncode:
87 raise Exception("git repack returned %d" % output.returncode)
88 return out
89
90
91def random_sha1():
92 return hashlib.sha1(str(random.random())).hexdigest()
93
94
James E. Blaira190f3b2015-01-05 14:56:54 -080095def iterate_timeout(max_seconds, purpose):
96 start = time.time()
97 count = 0
98 while (time.time() < start + max_seconds):
99 count += 1
100 yield count
101 time.sleep(0)
102 raise Exception("Timeout waiting for %s" % purpose)
103
104
Jesse Keating436a5452017-04-20 11:48:41 -0700105def simple_layout(path, driver='gerrit'):
James E. Blair06cc3922017-04-19 10:08:10 -0700106 """Specify a layout file for use by a test method.
107
108 :arg str path: The path to the layout file.
Jesse Keating436a5452017-04-20 11:48:41 -0700109 :arg str driver: The source driver to use, defaults to gerrit.
James E. Blair06cc3922017-04-19 10:08:10 -0700110
111 Some tests require only a very simple configuration. For those,
112 establishing a complete config directory hierachy is too much
113 work. In those cases, you can add a simple zuul.yaml file to the
114 test fixtures directory (in fixtures/layouts/foo.yaml) and use
115 this decorator to indicate the test method should use that rather
116 than the tenant config file specified by the test class.
117
118 The decorator will cause that layout file to be added to a
119 config-project called "common-config" and each "project" instance
120 referenced in the layout file will have a git repo automatically
121 initialized.
122 """
123
124 def decorator(test):
Jesse Keating436a5452017-04-20 11:48:41 -0700125 test.__simple_layout__ = (path, driver)
James E. Blair06cc3922017-04-19 10:08:10 -0700126 return test
127 return decorator
128
129
Gregory Haynes4fc12542015-04-22 20:38:06 -0700130class GerritChangeReference(git.Reference):
Clark Boylanb640e052014-04-03 16:41:46 -0700131 _common_path_default = "refs/changes"
132 _points_to_commits_only = True
133
134
Gregory Haynes4fc12542015-04-22 20:38:06 -0700135class FakeGerritChange(object):
James E. Blair8b5408c2016-08-08 15:37:46 -0700136 categories = {'approved': ('Approved', -1, 1),
137 'code-review': ('Code-Review', -2, 2),
138 'verified': ('Verified', -2, 2)}
Clark Boylanb640e052014-04-03 16:41:46 -0700139
140 def __init__(self, gerrit, number, project, branch, subject,
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700141 status='NEW', upstream_root=None, files={}):
Clark Boylanb640e052014-04-03 16:41:46 -0700142 self.gerrit = gerrit
Gregory Haynes4fc12542015-04-22 20:38:06 -0700143 self.source = gerrit
Clark Boylanb640e052014-04-03 16:41:46 -0700144 self.reported = 0
145 self.queried = 0
146 self.patchsets = []
147 self.number = number
148 self.project = project
149 self.branch = branch
150 self.subject = subject
151 self.latest_patchset = 0
152 self.depends_on_change = None
153 self.needed_by_changes = []
154 self.fail_merge = False
155 self.messages = []
156 self.data = {
157 'branch': branch,
158 'comments': [],
159 'commitMessage': subject,
160 'createdOn': time.time(),
161 'id': 'I' + random_sha1(),
162 'lastUpdated': time.time(),
163 'number': str(number),
164 'open': status == 'NEW',
165 'owner': {'email': 'user@example.com',
166 'name': 'User Name',
167 'username': 'username'},
168 'patchSets': self.patchsets,
169 'project': project,
170 'status': status,
171 'subject': subject,
172 'submitRecords': [],
173 'url': 'https://hostname/%s' % number}
174
175 self.upstream_root = upstream_root
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700176 self.addPatchset(files=files)
Clark Boylanb640e052014-04-03 16:41:46 -0700177 self.data['submitRecords'] = self.getSubmitRecords()
178 self.open = status == 'NEW'
179
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700180 def addFakeChangeToRepo(self, msg, files, large):
Clark Boylanb640e052014-04-03 16:41:46 -0700181 path = os.path.join(self.upstream_root, self.project)
182 repo = git.Repo(path)
Gregory Haynes4fc12542015-04-22 20:38:06 -0700183 ref = GerritChangeReference.create(
184 repo, '1/%s/%s' % (self.number, self.latest_patchset),
185 'refs/tags/init')
Clark Boylanb640e052014-04-03 16:41:46 -0700186 repo.head.reference = ref
James E. Blair879dafb2015-07-17 14:04:49 -0700187 zuul.merger.merger.reset_repo_to_head(repo)
Clark Boylanb640e052014-04-03 16:41:46 -0700188 repo.git.clean('-x', '-f', '-d')
189
190 path = os.path.join(self.upstream_root, self.project)
191 if not large:
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700192 for fn, content in files.items():
193 fn = os.path.join(path, fn)
194 with open(fn, 'w') as f:
195 f.write(content)
196 repo.index.add([fn])
Clark Boylanb640e052014-04-03 16:41:46 -0700197 else:
198 for fni in range(100):
199 fn = os.path.join(path, str(fni))
200 f = open(fn, 'w')
201 for ci in range(4096):
202 f.write(random.choice(string.printable))
203 f.close()
204 repo.index.add([fn])
205
206 r = repo.index.commit(msg)
207 repo.head.reference = 'master'
James E. Blair879dafb2015-07-17 14:04:49 -0700208 zuul.merger.merger.reset_repo_to_head(repo)
Clark Boylanb640e052014-04-03 16:41:46 -0700209 repo.git.clean('-x', '-f', '-d')
210 repo.heads['master'].checkout()
211 return r
212
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700213 def addPatchset(self, files=None, large=False):
Clark Boylanb640e052014-04-03 16:41:46 -0700214 self.latest_patchset += 1
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700215 if not files:
James E. Blair97d902e2014-08-21 13:25:56 -0700216 fn = '%s-%s' % (self.branch.replace('/', '_'), self.number)
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700217 data = ("test %s %s %s\n" %
218 (self.branch, self.number, self.latest_patchset))
219 files = {fn: data}
Clark Boylanb640e052014-04-03 16:41:46 -0700220 msg = self.subject + '-' + str(self.latest_patchset)
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700221 c = self.addFakeChangeToRepo(msg, files, large)
Clark Boylanb640e052014-04-03 16:41:46 -0700222 ps_files = [{'file': '/COMMIT_MSG',
223 'type': 'ADDED'},
224 {'file': 'README',
225 'type': 'MODIFIED'}]
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700226 for f in files.keys():
Clark Boylanb640e052014-04-03 16:41:46 -0700227 ps_files.append({'file': f, 'type': 'ADDED'})
228 d = {'approvals': [],
229 'createdOn': time.time(),
230 'files': ps_files,
231 'number': str(self.latest_patchset),
232 'ref': 'refs/changes/1/%s/%s' % (self.number,
233 self.latest_patchset),
234 'revision': c.hexsha,
235 'uploader': {'email': 'user@example.com',
236 'name': 'User name',
237 'username': 'user'}}
238 self.data['currentPatchSet'] = d
239 self.patchsets.append(d)
240 self.data['submitRecords'] = self.getSubmitRecords()
241
242 def getPatchsetCreatedEvent(self, patchset):
243 event = {"type": "patchset-created",
244 "change": {"project": self.project,
245 "branch": self.branch,
246 "id": "I5459869c07352a31bfb1e7a8cac379cabfcb25af",
247 "number": str(self.number),
248 "subject": self.subject,
249 "owner": {"name": "User Name"},
250 "url": "https://hostname/3"},
251 "patchSet": self.patchsets[patchset - 1],
252 "uploader": {"name": "User Name"}}
253 return event
254
255 def getChangeRestoredEvent(self):
256 event = {"type": "change-restored",
257 "change": {"project": self.project,
258 "branch": self.branch,
259 "id": "I5459869c07352a31bfb1e7a8cac379cabfcb25af",
260 "number": str(self.number),
261 "subject": self.subject,
262 "owner": {"name": "User Name"},
263 "url": "https://hostname/3"},
264 "restorer": {"name": "User Name"},
Antoine Mussobd86a312014-01-08 14:51:33 +0100265 "patchSet": self.patchsets[-1],
266 "reason": ""}
267 return event
268
269 def getChangeAbandonedEvent(self):
270 event = {"type": "change-abandoned",
271 "change": {"project": self.project,
272 "branch": self.branch,
273 "id": "I5459869c07352a31bfb1e7a8cac379cabfcb25af",
274 "number": str(self.number),
275 "subject": self.subject,
276 "owner": {"name": "User Name"},
277 "url": "https://hostname/3"},
278 "abandoner": {"name": "User Name"},
279 "patchSet": self.patchsets[-1],
Clark Boylanb640e052014-04-03 16:41:46 -0700280 "reason": ""}
281 return event
282
283 def getChangeCommentEvent(self, patchset):
284 event = {"type": "comment-added",
285 "change": {"project": self.project,
286 "branch": self.branch,
287 "id": "I5459869c07352a31bfb1e7a8cac379cabfcb25af",
288 "number": str(self.number),
289 "subject": self.subject,
290 "owner": {"name": "User Name"},
291 "url": "https://hostname/3"},
292 "patchSet": self.patchsets[patchset - 1],
293 "author": {"name": "User Name"},
James E. Blair8b5408c2016-08-08 15:37:46 -0700294 "approvals": [{"type": "code-review",
Clark Boylanb640e052014-04-03 16:41:46 -0700295 "description": "Code-Review",
296 "value": "0"}],
297 "comment": "This is a comment"}
298 return event
299
James E. Blairc2a5ed72017-02-20 14:12:01 -0500300 def getChangeMergedEvent(self):
301 event = {"submitter": {"name": "Jenkins",
302 "username": "jenkins"},
303 "newRev": "29ed3b5f8f750a225c5be70235230e3a6ccb04d9",
304 "patchSet": self.patchsets[-1],
305 "change": self.data,
306 "type": "change-merged",
307 "eventCreatedOn": 1487613810}
308 return event
309
James E. Blair8cce42e2016-10-18 08:18:36 -0700310 def getRefUpdatedEvent(self):
311 path = os.path.join(self.upstream_root, self.project)
312 repo = git.Repo(path)
313 oldrev = repo.heads[self.branch].commit.hexsha
314
315 event = {
316 "type": "ref-updated",
317 "submitter": {
318 "name": "User Name",
319 },
320 "refUpdate": {
321 "oldRev": oldrev,
322 "newRev": self.patchsets[-1]['revision'],
323 "refName": self.branch,
324 "project": self.project,
325 }
326 }
327 return event
328
Joshua Hesketh642824b2014-07-01 17:54:59 +1000329 def addApproval(self, category, value, username='reviewer_john',
330 granted_on=None, message=''):
Clark Boylanb640e052014-04-03 16:41:46 -0700331 if not granted_on:
332 granted_on = time.time()
Joshua Hesketh29d99b72014-08-19 16:27:42 +1000333 approval = {
334 'description': self.categories[category][0],
335 'type': category,
336 'value': str(value),
337 'by': {
338 'username': username,
339 'email': username + '@example.com',
340 },
341 'grantedOn': int(granted_on)
342 }
Clark Boylanb640e052014-04-03 16:41:46 -0700343 for i, x in enumerate(self.patchsets[-1]['approvals'][:]):
344 if x['by']['username'] == username and x['type'] == category:
345 del self.patchsets[-1]['approvals'][i]
346 self.patchsets[-1]['approvals'].append(approval)
347 event = {'approvals': [approval],
Joshua Hesketh642824b2014-07-01 17:54:59 +1000348 'author': {'email': 'author@example.com',
349 'name': 'Patchset Author',
350 'username': 'author_phil'},
Clark Boylanb640e052014-04-03 16:41:46 -0700351 'change': {'branch': self.branch,
352 'id': 'Iaa69c46accf97d0598111724a38250ae76a22c87',
353 'number': str(self.number),
Joshua Hesketh642824b2014-07-01 17:54:59 +1000354 'owner': {'email': 'owner@example.com',
355 'name': 'Change Owner',
356 'username': 'owner_jane'},
Clark Boylanb640e052014-04-03 16:41:46 -0700357 'project': self.project,
358 'subject': self.subject,
359 'topic': 'master',
360 'url': 'https://hostname/459'},
Joshua Hesketh642824b2014-07-01 17:54:59 +1000361 'comment': message,
Clark Boylanb640e052014-04-03 16:41:46 -0700362 'patchSet': self.patchsets[-1],
363 'type': 'comment-added'}
364 self.data['submitRecords'] = self.getSubmitRecords()
365 return json.loads(json.dumps(event))
366
367 def getSubmitRecords(self):
368 status = {}
369 for cat in self.categories.keys():
370 status[cat] = 0
371
372 for a in self.patchsets[-1]['approvals']:
373 cur = status[a['type']]
374 cat_min, cat_max = self.categories[a['type']][1:]
375 new = int(a['value'])
376 if new == cat_min:
377 cur = new
378 elif abs(new) > abs(cur):
379 cur = new
380 status[a['type']] = cur
381
382 labels = []
383 ok = True
384 for typ, cat in self.categories.items():
385 cur = status[typ]
386 cat_min, cat_max = cat[1:]
387 if cur == cat_min:
388 value = 'REJECT'
389 ok = False
390 elif cur == cat_max:
391 value = 'OK'
392 else:
393 value = 'NEED'
394 ok = False
395 labels.append({'label': cat[0], 'status': value})
396 if ok:
397 return [{'status': 'OK'}]
398 return [{'status': 'NOT_READY',
399 'labels': labels}]
400
401 def setDependsOn(self, other, patchset):
402 self.depends_on_change = other
403 d = {'id': other.data['id'],
404 'number': other.data['number'],
405 'ref': other.patchsets[patchset - 1]['ref']
406 }
407 self.data['dependsOn'] = [d]
408
409 other.needed_by_changes.append(self)
410 needed = other.data.get('neededBy', [])
411 d = {'id': self.data['id'],
412 'number': self.data['number'],
413 'ref': self.patchsets[patchset - 1]['ref'],
414 'revision': self.patchsets[patchset - 1]['revision']
415 }
416 needed.append(d)
417 other.data['neededBy'] = needed
418
419 def query(self):
420 self.queried += 1
421 d = self.data.get('dependsOn')
422 if d:
423 d = d[0]
424 if (self.depends_on_change.patchsets[-1]['ref'] == d['ref']):
425 d['isCurrentPatchSet'] = True
426 else:
427 d['isCurrentPatchSet'] = False
428 return json.loads(json.dumps(self.data))
429
430 def setMerged(self):
431 if (self.depends_on_change and
Joshua Hesketh29d99b72014-08-19 16:27:42 +1000432 self.depends_on_change.data['status'] != 'MERGED'):
Clark Boylanb640e052014-04-03 16:41:46 -0700433 return
434 if self.fail_merge:
435 return
436 self.data['status'] = 'MERGED'
437 self.open = False
438
439 path = os.path.join(self.upstream_root, self.project)
440 repo = git.Repo(path)
441 repo.heads[self.branch].commit = \
442 repo.commit(self.patchsets[-1]['revision'])
443
444 def setReported(self):
445 self.reported += 1
446
447
James E. Blaire511d2f2016-12-08 15:22:26 -0800448class FakeGerritConnection(gerritconnection.GerritConnection):
James E. Blaire7b99a02016-08-05 14:27:34 -0700449 """A Fake Gerrit connection for use in tests.
450
451 This subclasses
452 :py:class:`~zuul.connection.gerrit.GerritConnection` to add the
453 ability for tests to add changes to the fake Gerrit it represents.
454 """
455
Joshua Hesketh352264b2015-08-11 23:42:08 +1000456 log = logging.getLogger("zuul.test.FakeGerritConnection")
James E. Blair96698e22015-04-02 07:48:21 -0700457
James E. Blaire511d2f2016-12-08 15:22:26 -0800458 def __init__(self, driver, connection_name, connection_config,
James E. Blair7fc8daa2016-08-08 15:37:15 -0700459 changes_db=None, upstream_root=None):
James E. Blaire511d2f2016-12-08 15:22:26 -0800460 super(FakeGerritConnection, self).__init__(driver, connection_name,
Joshua Hesketh352264b2015-08-11 23:42:08 +1000461 connection_config)
462
James E. Blair7fc8daa2016-08-08 15:37:15 -0700463 self.event_queue = Queue.Queue()
Clark Boylanb640e052014-04-03 16:41:46 -0700464 self.fixture_dir = os.path.join(FIXTURE_DIR, 'gerrit')
465 self.change_number = 0
Joshua Hesketh352264b2015-08-11 23:42:08 +1000466 self.changes = changes_db
James E. Blairf8ff9932014-08-15 15:24:24 -0700467 self.queries = []
Jan Hruban6b71aff2015-10-22 16:58:08 +0200468 self.upstream_root = upstream_root
Clark Boylanb640e052014-04-03 16:41:46 -0700469
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700470 def addFakeChange(self, project, branch, subject, status='NEW',
471 files=None):
James E. Blaire7b99a02016-08-05 14:27:34 -0700472 """Add a change to the fake Gerrit."""
Clark Boylanb640e052014-04-03 16:41:46 -0700473 self.change_number += 1
Gregory Haynes4fc12542015-04-22 20:38:06 -0700474 c = FakeGerritChange(self, self.change_number, project, branch,
475 subject, upstream_root=self.upstream_root,
476 status=status, files=files)
Clark Boylanb640e052014-04-03 16:41:46 -0700477 self.changes[self.change_number] = c
478 return c
479
Clark Boylanb640e052014-04-03 16:41:46 -0700480 def review(self, project, changeid, message, action):
481 number, ps = changeid.split(',')
482 change = self.changes[int(number)]
Joshua Hesketh642824b2014-07-01 17:54:59 +1000483
484 # Add the approval back onto the change (ie simulate what gerrit would
485 # do).
486 # Usually when zuul leaves a review it'll create a feedback loop where
487 # zuul's review enters another gerrit event (which is then picked up by
488 # zuul). However, we can't mimic this behaviour (by adding this
489 # approval event into the queue) as it stops jobs from checking what
490 # happens before this event is triggered. If a job needs to see what
491 # happens they can add their own verified event into the queue.
492 # Nevertheless, we can update change with the new review in gerrit.
493
James E. Blair8b5408c2016-08-08 15:37:46 -0700494 for cat in action.keys():
495 if cat != 'submit':
Joshua Hesketh352264b2015-08-11 23:42:08 +1000496 change.addApproval(cat, action[cat], username=self.user)
Joshua Hesketh642824b2014-07-01 17:54:59 +1000497
James E. Blair8b5408c2016-08-08 15:37:46 -0700498 # TODOv3(jeblair): can this be removed?
Joshua Hesketh642824b2014-07-01 17:54:59 +1000499 if 'label' in action:
500 parts = action['label'].split('=')
Joshua Hesketh352264b2015-08-11 23:42:08 +1000501 change.addApproval(parts[0], parts[2], username=self.user)
Joshua Hesketh642824b2014-07-01 17:54:59 +1000502
Clark Boylanb640e052014-04-03 16:41:46 -0700503 change.messages.append(message)
Joshua Hesketh642824b2014-07-01 17:54:59 +1000504
Clark Boylanb640e052014-04-03 16:41:46 -0700505 if 'submit' in action:
506 change.setMerged()
507 if message:
508 change.setReported()
509
510 def query(self, number):
511 change = self.changes.get(int(number))
512 if change:
513 return change.query()
514 return {}
515
James E. Blairc494d542014-08-06 09:23:52 -0700516 def simpleQuery(self, query):
James E. Blair96698e22015-04-02 07:48:21 -0700517 self.log.debug("simpleQuery: %s" % query)
James E. Blairf8ff9932014-08-15 15:24:24 -0700518 self.queries.append(query)
James E. Blair5ee24252014-12-30 10:12:29 -0800519 if query.startswith('change:'):
520 # Query a specific changeid
521 changeid = query[len('change:'):]
522 l = [change.query() for change in self.changes.values()
523 if change.data['id'] == changeid]
James E. Blair96698e22015-04-02 07:48:21 -0700524 elif query.startswith('message:'):
525 # Query the content of a commit message
526 msg = query[len('message:'):].strip()
527 l = [change.query() for change in self.changes.values()
528 if msg in change.data['commitMessage']]
James E. Blair5ee24252014-12-30 10:12:29 -0800529 else:
530 # Query all open changes
531 l = [change.query() for change in self.changes.values()]
James E. Blairf8ff9932014-08-15 15:24:24 -0700532 return l
James E. Blairc494d542014-08-06 09:23:52 -0700533
Joshua Hesketh352264b2015-08-11 23:42:08 +1000534 def _start_watcher_thread(self, *args, **kw):
Clark Boylanb640e052014-04-03 16:41:46 -0700535 pass
536
Joshua Hesketh352264b2015-08-11 23:42:08 +1000537 def getGitUrl(self, project):
538 return os.path.join(self.upstream_root, project.name)
539
Clark Boylanb640e052014-04-03 16:41:46 -0700540
Gregory Haynes4fc12542015-04-22 20:38:06 -0700541class GithubChangeReference(git.Reference):
542 _common_path_default = "refs/pull"
543 _points_to_commits_only = True
544
545
546class FakeGithubPullRequest(object):
547
548 def __init__(self, github, number, project, branch,
549 upstream_root, number_of_commits=1):
550 """Creates a new PR with several commits.
551 Sends an event about opened PR."""
552 self.github = github
553 self.source = github
554 self.number = number
555 self.project = project
556 self.branch = branch
557 self.upstream_root = upstream_root
558 self.comments = []
Jan Hrubane252a732017-01-03 15:03:09 +0100559 self.statuses = {}
Gregory Haynes4fc12542015-04-22 20:38:06 -0700560 self.updated_at = None
561 self.head_sha = None
562 self._createPRRef()
563 self._addCommitToRepo()
564 self._updateTimeStamp()
565
566 def addCommit(self):
567 """Adds a commit on top of the actual PR head."""
568 self._addCommitToRepo()
569 self._updateTimeStamp()
Jan Hrubane252a732017-01-03 15:03:09 +0100570 self._clearStatuses()
Gregory Haynes4fc12542015-04-22 20:38:06 -0700571
572 def forcePush(self):
573 """Clears actual commits and add a commit on top of the base."""
574 self._addCommitToRepo(reset=True)
575 self._updateTimeStamp()
Jan Hrubane252a732017-01-03 15:03:09 +0100576 self._clearStatuses()
Gregory Haynes4fc12542015-04-22 20:38:06 -0700577
578 def getPullRequestOpenedEvent(self):
579 return self._getPullRequestEvent('opened')
580
581 def getPullRequestSynchronizeEvent(self):
582 return self._getPullRequestEvent('synchronize')
583
584 def getPullRequestReopenedEvent(self):
585 return self._getPullRequestEvent('reopened')
586
587 def getPullRequestClosedEvent(self):
588 return self._getPullRequestEvent('closed')
589
590 def addComment(self, message):
591 self.comments.append(message)
592 self._updateTimeStamp()
593
Jan Hrubanc7ab1602015-10-14 15:29:33 +0200594 def getCommentAddedEvent(self, text):
595 name = 'issue_comment'
596 data = {
597 'action': 'created',
598 'issue': {
599 'number': self.number
600 },
601 'comment': {
602 'body': text
603 },
604 'repository': {
605 'full_name': self.project
606 }
607 }
608 return (name, data)
609
Gregory Haynes4fc12542015-04-22 20:38:06 -0700610 def _getRepo(self):
611 repo_path = os.path.join(self.upstream_root, self.project)
612 return git.Repo(repo_path)
613
614 def _createPRRef(self):
615 repo = self._getRepo()
616 GithubChangeReference.create(
617 repo, self._getPRReference(), 'refs/tags/init')
618
619 def _addCommitToRepo(self, reset=False):
620 repo = self._getRepo()
621 ref = repo.references[self._getPRReference()]
622 if reset:
623 ref.set_object('refs/tags/init')
624 repo.head.reference = ref
625 zuul.merger.merger.reset_repo_to_head(repo)
626 repo.git.clean('-x', '-f', '-d')
627
628 fn = '%s-%s' % (self.branch.replace('/', '_'), self.number)
629 msg = 'test-%s' % self.number
630 fn = os.path.join(repo.working_dir, fn)
631 f = open(fn, 'w')
632 with open(fn, 'w') as f:
633 f.write("test %s %s\n" %
634 (self.branch, self.number))
635 repo.index.add([fn])
636
637 self.head_sha = repo.index.commit(msg).hexsha
638 repo.head.reference = 'master'
639 zuul.merger.merger.reset_repo_to_head(repo)
640 repo.git.clean('-x', '-f', '-d')
641 repo.heads['master'].checkout()
642
643 def _updateTimeStamp(self):
644 self.updated_at = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.localtime())
645
646 def getPRHeadSha(self):
647 repo = self._getRepo()
648 return repo.references[self._getPRReference()].commit.hexsha
649
Jan Hrubane252a732017-01-03 15:03:09 +0100650 def setStatus(self, state, url, description, context):
651 self.statuses[context] = {
652 'state': state,
653 'url': url,
654 'description': description
655 }
656
657 def _clearStatuses(self):
658 self.statuses = {}
659
Gregory Haynes4fc12542015-04-22 20:38:06 -0700660 def _getPRReference(self):
661 return '%s/head' % self.number
662
663 def _getPullRequestEvent(self, action):
664 name = 'pull_request'
665 data = {
666 'action': action,
667 'number': self.number,
668 'pull_request': {
669 'number': self.number,
670 'updated_at': self.updated_at,
671 'base': {
672 'ref': self.branch,
673 'repo': {
674 'full_name': self.project
675 }
676 },
677 'head': {
678 'sha': self.head_sha
679 }
680 }
681 }
682 return (name, data)
683
684
685class FakeGithubConnection(githubconnection.GithubConnection):
686 log = logging.getLogger("zuul.test.FakeGithubConnection")
687
688 def __init__(self, driver, connection_name, connection_config,
689 upstream_root=None):
690 super(FakeGithubConnection, self).__init__(driver, connection_name,
691 connection_config)
692 self.connection_name = connection_name
693 self.pr_number = 0
694 self.pull_requests = []
695 self.upstream_root = upstream_root
696
697 def openFakePullRequest(self, project, branch):
698 self.pr_number += 1
699 pull_request = FakeGithubPullRequest(
700 self, self.pr_number, project, branch, self.upstream_root)
701 self.pull_requests.append(pull_request)
702 return pull_request
703
Wayne1a78c612015-06-11 17:14:13 -0700704 def getPushEvent(self, project, ref, old_rev=None, new_rev=None):
705 if not old_rev:
706 old_rev = '00000000000000000000000000000000'
707 if not new_rev:
708 new_rev = random_sha1()
709 name = 'push'
710 data = {
711 'ref': ref,
712 'before': old_rev,
713 'after': new_rev,
714 'repository': {
715 'full_name': project
716 }
717 }
718 return (name, data)
719
Gregory Haynes4fc12542015-04-22 20:38:06 -0700720 def emitEvent(self, event):
721 """Emulates sending the GitHub webhook event to the connection."""
722 port = self.webapp.server.socket.getsockname()[1]
723 name, data = event
724 payload = json.dumps(data)
725 headers = {'X-Github-Event': name}
726 req = urllib.request.Request(
727 'http://localhost:%s/connection/%s/payload'
728 % (port, self.connection_name),
729 data=payload, headers=headers)
730 urllib.request.urlopen(req)
731
Jan Hrubanc7ab1602015-10-14 15:29:33 +0200732 def getPull(self, project, number):
733 pr = self.pull_requests[number - 1]
734 data = {
735 'number': number,
736 'updated_at': pr.updated_at,
737 'base': {
738 'repo': {
739 'full_name': pr.project
740 },
741 'ref': pr.branch,
742 },
743 'head': {
744 'sha': pr.head_sha
745 }
746 }
747 return data
748
Gregory Haynes4fc12542015-04-22 20:38:06 -0700749 def getGitUrl(self, project):
750 return os.path.join(self.upstream_root, str(project))
751
Jan Hruban6d53c5e2015-10-24 03:03:34 +0200752 def real_getGitUrl(self, project):
753 return super(FakeGithubConnection, self).getGitUrl(project)
754
Gregory Haynes4fc12542015-04-22 20:38:06 -0700755 def getProjectBranches(self, project):
756 """Masks getProjectBranches since we don't have a real github"""
757
758 # just returns master for now
759 return ['master']
760
Jan Hrubane252a732017-01-03 15:03:09 +0100761 def commentPull(self, project, pr_number, message):
Wayne40f40042015-06-12 16:56:30 -0700762 pull_request = self.pull_requests[pr_number - 1]
763 pull_request.addComment(message)
764
Jan Hrubane252a732017-01-03 15:03:09 +0100765 def setCommitStatus(self, project, sha, state,
766 url='', description='', context=''):
767 owner, proj = project.split('/')
768 for pr in self.pull_requests:
769 pr_owner, pr_project = pr.project.split('/')
770 if (pr_owner == owner and pr_project == proj and
771 pr.head_sha == sha):
772 pr.setStatus(state, url, description, context)
773
Gregory Haynes4fc12542015-04-22 20:38:06 -0700774
Clark Boylanb640e052014-04-03 16:41:46 -0700775class BuildHistory(object):
776 def __init__(self, **kw):
777 self.__dict__.update(kw)
778
779 def __repr__(self):
James E. Blair17302972016-08-10 16:11:42 -0700780 return ("<Completed build, result: %s name: %s uuid: %s changes: %s>" %
781 (self.result, self.name, self.uuid, self.changes))
Clark Boylanb640e052014-04-03 16:41:46 -0700782
783
784class FakeURLOpener(object):
Jan Hruban6b71aff2015-10-22 16:58:08 +0200785 def __init__(self, upstream_root, url):
Clark Boylanb640e052014-04-03 16:41:46 -0700786 self.upstream_root = upstream_root
Clark Boylanb640e052014-04-03 16:41:46 -0700787 self.url = url
788
789 def read(self):
Morgan Fainberg293f7f82016-05-30 14:01:22 -0700790 res = urllib.parse.urlparse(self.url)
Clark Boylanb640e052014-04-03 16:41:46 -0700791 path = res.path
792 project = '/'.join(path.split('/')[2:-2])
793 ret = '001e# service=git-upload-pack\n'
794 ret += ('000000a31270149696713ba7e06f1beb760f20d359c4abed HEAD\x00'
795 'multi_ack thin-pack side-band side-band-64k ofs-delta '
796 'shallow no-progress include-tag multi_ack_detailed no-done\n')
797 path = os.path.join(self.upstream_root, project)
798 repo = git.Repo(path)
799 for ref in repo.refs:
800 r = ref.object.hexsha + ' ' + ref.path + '\n'
801 ret += '%04x%s' % (len(r) + 4, r)
802 ret += '0000'
803 return ret
804
805
Clark Boylanb640e052014-04-03 16:41:46 -0700806class FakeStatsd(threading.Thread):
807 def __init__(self):
808 threading.Thread.__init__(self)
809 self.daemon = True
810 self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
811 self.sock.bind(('', 0))
812 self.port = self.sock.getsockname()[1]
813 self.wake_read, self.wake_write = os.pipe()
814 self.stats = []
815
816 def run(self):
817 while True:
818 poll = select.poll()
819 poll.register(self.sock, select.POLLIN)
820 poll.register(self.wake_read, select.POLLIN)
821 ret = poll.poll()
822 for (fd, event) in ret:
823 if fd == self.sock.fileno():
824 data = self.sock.recvfrom(1024)
825 if not data:
826 return
827 self.stats.append(data[0])
828 if fd == self.wake_read:
829 return
830
831 def stop(self):
832 os.write(self.wake_write, '1\n')
833
834
James E. Blaire1767bc2016-08-02 10:00:27 -0700835class FakeBuild(object):
Clark Boylanb640e052014-04-03 16:41:46 -0700836 log = logging.getLogger("zuul.test")
837
Paul Belanger174a8272017-03-14 13:20:10 -0400838 def __init__(self, executor_server, job):
Clark Boylanb640e052014-04-03 16:41:46 -0700839 self.daemon = True
Paul Belanger174a8272017-03-14 13:20:10 -0400840 self.executor_server = executor_server
Clark Boylanb640e052014-04-03 16:41:46 -0700841 self.job = job
James E. Blairab7132b2016-08-05 12:36:22 -0700842 self.jobdir = None
James E. Blair17302972016-08-10 16:11:42 -0700843 self.uuid = job.unique
Clark Boylanb640e052014-04-03 16:41:46 -0700844 self.parameters = json.loads(job.arguments)
James E. Blair34776ee2016-08-25 13:53:54 -0700845 # TODOv3(jeblair): self.node is really "the image of the node
846 # assigned". We should rename it (self.node_image?) if we
847 # keep using it like this, or we may end up exposing more of
848 # the complexity around multi-node jobs here
849 # (self.nodes[0].image?)
850 self.node = None
851 if len(self.parameters.get('nodes')) == 1:
852 self.node = self.parameters['nodes'][0]['image']
Clark Boylanb640e052014-04-03 16:41:46 -0700853 self.unique = self.parameters['ZUUL_UUID']
Jamie Lennoxd2e37332016-12-05 15:26:19 +1100854 self.pipeline = self.parameters['ZUUL_PIPELINE']
855 self.project = self.parameters['ZUUL_PROJECT']
James E. Blair3f876d52016-07-22 13:07:14 -0700856 self.name = self.parameters['job']
Clark Boylanb640e052014-04-03 16:41:46 -0700857 self.wait_condition = threading.Condition()
858 self.waiting = False
859 self.aborted = False
Paul Belanger71d98172016-11-08 10:56:31 -0500860 self.requeue = False
Clark Boylanb640e052014-04-03 16:41:46 -0700861 self.created = time.time()
James E. Blaire1767bc2016-08-02 10:00:27 -0700862 self.changes = None
863 if 'ZUUL_CHANGE_IDS' in self.parameters:
864 self.changes = self.parameters['ZUUL_CHANGE_IDS']
Clark Boylanb640e052014-04-03 16:41:46 -0700865
James E. Blair3158e282016-08-19 09:34:11 -0700866 def __repr__(self):
867 waiting = ''
868 if self.waiting:
869 waiting = ' [waiting]'
Jamie Lennoxd2e37332016-12-05 15:26:19 +1100870 return '<FakeBuild %s:%s %s%s>' % (self.pipeline, self.name,
871 self.changes, waiting)
James E. Blair3158e282016-08-19 09:34:11 -0700872
Clark Boylanb640e052014-04-03 16:41:46 -0700873 def release(self):
James E. Blaire7b99a02016-08-05 14:27:34 -0700874 """Release this build."""
Clark Boylanb640e052014-04-03 16:41:46 -0700875 self.wait_condition.acquire()
876 self.wait_condition.notify()
877 self.waiting = False
878 self.log.debug("Build %s released" % self.unique)
879 self.wait_condition.release()
880
881 def isWaiting(self):
James E. Blaire7b99a02016-08-05 14:27:34 -0700882 """Return whether this build is being held.
883
884 :returns: Whether the build is being held.
885 :rtype: bool
886 """
887
Clark Boylanb640e052014-04-03 16:41:46 -0700888 self.wait_condition.acquire()
889 if self.waiting:
890 ret = True
891 else:
892 ret = False
893 self.wait_condition.release()
894 return ret
895
896 def _wait(self):
897 self.wait_condition.acquire()
898 self.waiting = True
899 self.log.debug("Build %s waiting" % self.unique)
900 self.wait_condition.wait()
901 self.wait_condition.release()
902
903 def run(self):
Clark Boylanb640e052014-04-03 16:41:46 -0700904 self.log.debug('Running build %s' % self.unique)
905
Paul Belanger174a8272017-03-14 13:20:10 -0400906 if self.executor_server.hold_jobs_in_build:
Clark Boylanb640e052014-04-03 16:41:46 -0700907 self.log.debug('Holding build %s' % self.unique)
908 self._wait()
909 self.log.debug("Build %s continuing" % self.unique)
910
James E. Blair412fba82017-01-26 15:00:50 -0800911 result = (RecordingAnsibleJob.RESULT_NORMAL, 0) # Success
James E. Blaira5dba232016-08-08 15:53:24 -0700912 if (('ZUUL_REF' in self.parameters) and self.shouldFail()):
James E. Blair412fba82017-01-26 15:00:50 -0800913 result = (RecordingAnsibleJob.RESULT_NORMAL, 1) # Failure
Clark Boylanb640e052014-04-03 16:41:46 -0700914 if self.aborted:
James E. Blair412fba82017-01-26 15:00:50 -0800915 result = (RecordingAnsibleJob.RESULT_ABORTED, None)
Paul Belanger71d98172016-11-08 10:56:31 -0500916 if self.requeue:
James E. Blair412fba82017-01-26 15:00:50 -0800917 result = (RecordingAnsibleJob.RESULT_UNREACHABLE, None)
Clark Boylanb640e052014-04-03 16:41:46 -0700918
James E. Blaire1767bc2016-08-02 10:00:27 -0700919 return result
Clark Boylanb640e052014-04-03 16:41:46 -0700920
James E. Blaira5dba232016-08-08 15:53:24 -0700921 def shouldFail(self):
Paul Belanger174a8272017-03-14 13:20:10 -0400922 changes = self.executor_server.fail_tests.get(self.name, [])
James E. Blaira5dba232016-08-08 15:53:24 -0700923 for change in changes:
924 if self.hasChanges(change):
925 return True
926 return False
927
James E. Blaire7b99a02016-08-05 14:27:34 -0700928 def hasChanges(self, *changes):
929 """Return whether this build has certain changes in its git repos.
930
931 :arg FakeChange changes: One or more changes (varargs) that
Clark Boylan500992b2017-04-03 14:28:24 -0700932 are expected to be present (in order) in the git repository of
933 the active project.
James E. Blaire7b99a02016-08-05 14:27:34 -0700934
935 :returns: Whether the build has the indicated changes.
936 :rtype: bool
937
938 """
Clint Byrum3343e3e2016-11-15 16:05:03 -0800939 for change in changes:
Gregory Haynes4fc12542015-04-22 20:38:06 -0700940 hostname = change.source.canonical_hostname
James E. Blair2a535672017-04-27 12:03:15 -0700941 path = os.path.join(self.jobdir.src_root, hostname, change.project)
Clint Byrum3343e3e2016-11-15 16:05:03 -0800942 try:
943 repo = git.Repo(path)
944 except NoSuchPathError as e:
945 self.log.debug('%s' % e)
946 return False
947 ref = self.parameters['ZUUL_REF']
948 repo_messages = [c.message.strip() for c in repo.iter_commits(ref)]
949 commit_message = '%s-1' % change.subject
950 self.log.debug("Checking if build %s has changes; commit_message "
951 "%s; repo_messages %s" % (self, commit_message,
952 repo_messages))
953 if commit_message not in repo_messages:
James E. Blair962220f2016-08-03 11:22:38 -0700954 self.log.debug(" messages do not match")
955 return False
956 self.log.debug(" OK")
957 return True
958
Clark Boylanb640e052014-04-03 16:41:46 -0700959
Paul Belanger174a8272017-03-14 13:20:10 -0400960class RecordingExecutorServer(zuul.executor.server.ExecutorServer):
961 """An Ansible executor to be used in tests.
James E. Blaire7b99a02016-08-05 14:27:34 -0700962
Paul Belanger174a8272017-03-14 13:20:10 -0400963 :ivar bool hold_jobs_in_build: If true, when jobs are executed
James E. Blaire7b99a02016-08-05 14:27:34 -0700964 they will report that they have started but then pause until
965 released before reporting completion. This attribute may be
966 changed at any time and will take effect for subsequently
Paul Belanger174a8272017-03-14 13:20:10 -0400967 executed builds, but previously held builds will still need to
James E. Blaire7b99a02016-08-05 14:27:34 -0700968 be explicitly released.
969
970 """
James E. Blairf5dbd002015-12-23 15:26:17 -0800971 def __init__(self, *args, **kw):
James E. Blaire1767bc2016-08-02 10:00:27 -0700972 self._run_ansible = kw.pop('_run_ansible', False)
James E. Blaira92cbc82017-01-23 14:56:49 -0800973 self._test_root = kw.pop('_test_root', False)
Paul Belanger174a8272017-03-14 13:20:10 -0400974 super(RecordingExecutorServer, self).__init__(*args, **kw)
James E. Blaire1767bc2016-08-02 10:00:27 -0700975 self.hold_jobs_in_build = False
976 self.lock = threading.Lock()
977 self.running_builds = []
James E. Blair3f876d52016-07-22 13:07:14 -0700978 self.build_history = []
James E. Blaire1767bc2016-08-02 10:00:27 -0700979 self.fail_tests = {}
James E. Blairab7132b2016-08-05 12:36:22 -0700980 self.job_builds = {}
James E. Blairf5dbd002015-12-23 15:26:17 -0800981
James E. Blaira5dba232016-08-08 15:53:24 -0700982 def failJob(self, name, change):
Paul Belanger174a8272017-03-14 13:20:10 -0400983 """Instruct the executor to report matching builds as failures.
James E. Blaire7b99a02016-08-05 14:27:34 -0700984
985 :arg str name: The name of the job to fail.
James E. Blaira5dba232016-08-08 15:53:24 -0700986 :arg Change change: The :py:class:`~tests.base.FakeChange`
987 instance which should cause the job to fail. This job
988 will also fail for changes depending on this change.
James E. Blaire7b99a02016-08-05 14:27:34 -0700989
990 """
James E. Blaire1767bc2016-08-02 10:00:27 -0700991 l = self.fail_tests.get(name, [])
992 l.append(change)
993 self.fail_tests[name] = l
James E. Blairf5dbd002015-12-23 15:26:17 -0800994
James E. Blair962220f2016-08-03 11:22:38 -0700995 def release(self, regex=None):
James E. Blaire7b99a02016-08-05 14:27:34 -0700996 """Release a held build.
997
998 :arg str regex: A regular expression which, if supplied, will
999 cause only builds with matching names to be released. If
1000 not supplied, all builds will be released.
1001
1002 """
James E. Blair962220f2016-08-03 11:22:38 -07001003 builds = self.running_builds[:]
1004 self.log.debug("Releasing build %s (%s)" % (regex,
1005 len(self.running_builds)))
1006 for build in builds:
1007 if not regex or re.match(regex, build.name):
1008 self.log.debug("Releasing build %s" %
1009 (build.parameters['ZUUL_UUID']))
1010 build.release()
1011 else:
1012 self.log.debug("Not releasing build %s" %
1013 (build.parameters['ZUUL_UUID']))
1014 self.log.debug("Done releasing builds %s (%s)" %
1015 (regex, len(self.running_builds)))
1016
Paul Belanger174a8272017-03-14 13:20:10 -04001017 def executeJob(self, job):
James E. Blair34776ee2016-08-25 13:53:54 -07001018 build = FakeBuild(self, job)
James E. Blaire1767bc2016-08-02 10:00:27 -07001019 job.build = build
James E. Blaire1767bc2016-08-02 10:00:27 -07001020 self.running_builds.append(build)
James E. Blairab7132b2016-08-05 12:36:22 -07001021 self.job_builds[job.unique] = build
James E. Blaira92cbc82017-01-23 14:56:49 -08001022 args = json.loads(job.arguments)
James E. Blair490cf042017-02-24 23:07:21 -05001023 args['vars']['zuul']['_test'] = dict(test_root=self._test_root)
James E. Blaira92cbc82017-01-23 14:56:49 -08001024 job.arguments = json.dumps(args)
Joshua Hesketh50c21782016-10-13 21:34:14 +11001025 self.job_workers[job.unique] = RecordingAnsibleJob(self, job)
1026 self.job_workers[job.unique].run()
James E. Blair17302972016-08-10 16:11:42 -07001027
1028 def stopJob(self, job):
1029 self.log.debug("handle stop")
1030 parameters = json.loads(job.arguments)
1031 uuid = parameters['uuid']
1032 for build in self.running_builds:
1033 if build.unique == uuid:
1034 build.aborted = True
1035 build.release()
Paul Belanger174a8272017-03-14 13:20:10 -04001036 super(RecordingExecutorServer, self).stopJob(job)
James E. Blairab7132b2016-08-05 12:36:22 -07001037
James E. Blaira002b032017-04-18 10:35:48 -07001038 def stop(self):
1039 for build in self.running_builds:
1040 build.release()
1041 super(RecordingExecutorServer, self).stop()
1042
Joshua Hesketh50c21782016-10-13 21:34:14 +11001043
Paul Belanger174a8272017-03-14 13:20:10 -04001044class RecordingAnsibleJob(zuul.executor.server.AnsibleJob):
K Jonathan Harkerae04e4c2017-03-15 19:07:11 -07001045 def doMergeChanges(self, items):
1046 # Get a merger in order to update the repos involved in this job.
1047 commit = super(RecordingAnsibleJob, self).doMergeChanges(items)
1048 if not commit: # merge conflict
1049 self.recordResult('MERGER_FAILURE')
1050 return commit
1051
1052 def recordResult(self, result):
Paul Belanger174a8272017-03-14 13:20:10 -04001053 build = self.executor_server.job_builds[self.job.unique]
Paul Belanger174a8272017-03-14 13:20:10 -04001054 self.executor_server.lock.acquire()
1055 self.executor_server.build_history.append(
James E. Blair17302972016-08-10 16:11:42 -07001056 BuildHistory(name=build.name, result=result, changes=build.changes,
1057 node=build.node, uuid=build.unique,
K Jonathan Harker2c1a6232017-02-21 14:34:08 -08001058 parameters=build.parameters, jobdir=build.jobdir,
James E. Blaire1767bc2016-08-02 10:00:27 -07001059 pipeline=build.parameters['ZUUL_PIPELINE'])
1060 )
Paul Belanger174a8272017-03-14 13:20:10 -04001061 self.executor_server.running_builds.remove(build)
1062 del self.executor_server.job_builds[self.job.unique]
1063 self.executor_server.lock.release()
K Jonathan Harkerae04e4c2017-03-15 19:07:11 -07001064
1065 def runPlaybooks(self, args):
1066 build = self.executor_server.job_builds[self.job.unique]
1067 build.jobdir = self.jobdir
1068
1069 result = super(RecordingAnsibleJob, self).runPlaybooks(args)
1070 self.recordResult(result)
James E. Blair412fba82017-01-26 15:00:50 -08001071 return result
1072
Monty Taylore6562aa2017-02-20 07:37:39 -05001073 def runAnsible(self, cmd, timeout, trusted=False):
Paul Belanger174a8272017-03-14 13:20:10 -04001074 build = self.executor_server.job_builds[self.job.unique]
James E. Blair412fba82017-01-26 15:00:50 -08001075
Paul Belanger174a8272017-03-14 13:20:10 -04001076 if self.executor_server._run_ansible:
Monty Taylorc231d932017-02-03 09:57:15 -06001077 result = super(RecordingAnsibleJob, self).runAnsible(
Monty Taylore6562aa2017-02-20 07:37:39 -05001078 cmd, timeout, trusted=trusted)
James E. Blair412fba82017-01-26 15:00:50 -08001079 else:
1080 result = build.run()
James E. Blaire1767bc2016-08-02 10:00:27 -07001081 return result
James E. Blairf5dbd002015-12-23 15:26:17 -08001082
James E. Blairad8dca02017-02-21 11:48:32 -05001083 def getHostList(self, args):
1084 self.log.debug("hostlist")
1085 hosts = super(RecordingAnsibleJob, self).getHostList(args)
Paul Belangerc5bf3752017-03-16 19:38:43 -04001086 for host in hosts:
1087 host['host_vars']['ansible_connection'] = 'local'
1088
1089 hosts.append(dict(
1090 name='localhost',
1091 host_vars=dict(ansible_connection='local'),
1092 host_keys=[]))
James E. Blairad8dca02017-02-21 11:48:32 -05001093 return hosts
1094
James E. Blairf5dbd002015-12-23 15:26:17 -08001095
Clark Boylanb640e052014-04-03 16:41:46 -07001096class FakeGearmanServer(gear.Server):
James E. Blaire7b99a02016-08-05 14:27:34 -07001097 """A Gearman server for use in tests.
1098
1099 :ivar bool hold_jobs_in_queue: If true, submitted jobs will be
1100 added to the queue but will not be distributed to workers
1101 until released. This attribute may be changed at any time and
1102 will take effect for subsequently enqueued jobs, but
1103 previously held jobs will still need to be explicitly
1104 released.
1105
1106 """
1107
Clark Boylanb640e052014-04-03 16:41:46 -07001108 def __init__(self):
1109 self.hold_jobs_in_queue = False
1110 super(FakeGearmanServer, self).__init__(0)
1111
1112 def getJobForConnection(self, connection, peek=False):
1113 for queue in [self.high_queue, self.normal_queue, self.low_queue]:
1114 for job in queue:
1115 if not hasattr(job, 'waiting'):
Paul Belanger174a8272017-03-14 13:20:10 -04001116 if job.name.startswith('executor:execute'):
Clark Boylanb640e052014-04-03 16:41:46 -07001117 job.waiting = self.hold_jobs_in_queue
1118 else:
1119 job.waiting = False
1120 if job.waiting:
1121 continue
1122 if job.name in connection.functions:
1123 if not peek:
1124 queue.remove(job)
1125 connection.related_jobs[job.handle] = job
1126 job.worker_connection = connection
1127 job.running = True
1128 return job
1129 return None
1130
1131 def release(self, regex=None):
James E. Blaire7b99a02016-08-05 14:27:34 -07001132 """Release a held job.
1133
1134 :arg str regex: A regular expression which, if supplied, will
1135 cause only jobs with matching names to be released. If
1136 not supplied, all jobs will be released.
1137 """
Clark Boylanb640e052014-04-03 16:41:46 -07001138 released = False
1139 qlen = (len(self.high_queue) + len(self.normal_queue) +
1140 len(self.low_queue))
1141 self.log.debug("releasing queued job %s (%s)" % (regex, qlen))
1142 for job in self.getQueue():
Paul Belanger174a8272017-03-14 13:20:10 -04001143 if job.name != 'executor:execute':
Clark Boylanb640e052014-04-03 16:41:46 -07001144 continue
Paul Belanger6ab6af72016-11-06 11:32:59 -05001145 parameters = json.loads(job.arguments)
1146 if not regex or re.match(regex, parameters.get('job')):
Clark Boylanb640e052014-04-03 16:41:46 -07001147 self.log.debug("releasing queued job %s" %
1148 job.unique)
1149 job.waiting = False
1150 released = True
1151 else:
1152 self.log.debug("not releasing queued job %s" %
1153 job.unique)
1154 if released:
1155 self.wakeConnections()
1156 qlen = (len(self.high_queue) + len(self.normal_queue) +
1157 len(self.low_queue))
1158 self.log.debug("done releasing queued jobs %s (%s)" % (regex, qlen))
1159
1160
1161class FakeSMTP(object):
1162 log = logging.getLogger('zuul.FakeSMTP')
1163
1164 def __init__(self, messages, server, port):
1165 self.server = server
1166 self.port = port
1167 self.messages = messages
1168
1169 def sendmail(self, from_email, to_email, msg):
1170 self.log.info("Sending email from %s, to %s, with msg %s" % (
1171 from_email, to_email, msg))
1172
1173 headers = msg.split('\n\n', 1)[0]
1174 body = msg.split('\n\n', 1)[1]
1175
1176 self.messages.append(dict(
1177 from_email=from_email,
1178 to_email=to_email,
1179 msg=msg,
1180 headers=headers,
1181 body=body,
1182 ))
1183
1184 return True
1185
1186 def quit(self):
1187 return True
1188
1189
James E. Blairdce6cea2016-12-20 16:45:32 -08001190class FakeNodepool(object):
1191 REQUEST_ROOT = '/nodepool/requests'
James E. Blaire18d4602017-01-05 11:17:28 -08001192 NODE_ROOT = '/nodepool/nodes'
James E. Blairdce6cea2016-12-20 16:45:32 -08001193
1194 log = logging.getLogger("zuul.test.FakeNodepool")
1195
1196 def __init__(self, host, port, chroot):
1197 self.client = kazoo.client.KazooClient(
1198 hosts='%s:%s%s' % (host, port, chroot))
1199 self.client.start()
1200 self._running = True
James E. Blair15be0e12017-01-03 13:45:20 -08001201 self.paused = False
James E. Blairdce6cea2016-12-20 16:45:32 -08001202 self.thread = threading.Thread(target=self.run)
1203 self.thread.daemon = True
1204 self.thread.start()
James E. Blair6ab79e02017-01-06 10:10:17 -08001205 self.fail_requests = set()
James E. Blairdce6cea2016-12-20 16:45:32 -08001206
1207 def stop(self):
1208 self._running = False
1209 self.thread.join()
1210 self.client.stop()
1211 self.client.close()
1212
1213 def run(self):
1214 while self._running:
1215 self._run()
1216 time.sleep(0.1)
1217
1218 def _run(self):
James E. Blair15be0e12017-01-03 13:45:20 -08001219 if self.paused:
1220 return
James E. Blairdce6cea2016-12-20 16:45:32 -08001221 for req in self.getNodeRequests():
1222 self.fulfillRequest(req)
1223
1224 def getNodeRequests(self):
1225 try:
1226 reqids = self.client.get_children(self.REQUEST_ROOT)
1227 except kazoo.exceptions.NoNodeError:
1228 return []
1229 reqs = []
1230 for oid in sorted(reqids):
1231 path = self.REQUEST_ROOT + '/' + oid
James E. Blair0ef64f82017-02-02 11:25:16 -08001232 try:
1233 data, stat = self.client.get(path)
1234 data = json.loads(data)
1235 data['_oid'] = oid
1236 reqs.append(data)
1237 except kazoo.exceptions.NoNodeError:
1238 pass
James E. Blairdce6cea2016-12-20 16:45:32 -08001239 return reqs
1240
James E. Blaire18d4602017-01-05 11:17:28 -08001241 def getNodes(self):
1242 try:
1243 nodeids = self.client.get_children(self.NODE_ROOT)
1244 except kazoo.exceptions.NoNodeError:
1245 return []
1246 nodes = []
1247 for oid in sorted(nodeids):
1248 path = self.NODE_ROOT + '/' + oid
1249 data, stat = self.client.get(path)
1250 data = json.loads(data)
1251 data['_oid'] = oid
1252 try:
1253 lockfiles = self.client.get_children(path + '/lock')
1254 except kazoo.exceptions.NoNodeError:
1255 lockfiles = []
1256 if lockfiles:
1257 data['_lock'] = True
1258 else:
1259 data['_lock'] = False
1260 nodes.append(data)
1261 return nodes
1262
James E. Blaira38c28e2017-01-04 10:33:20 -08001263 def makeNode(self, request_id, node_type):
1264 now = time.time()
1265 path = '/nodepool/nodes/'
1266 data = dict(type=node_type,
1267 provider='test-provider',
1268 region='test-region',
Paul Belanger30ba93a2017-03-16 16:28:10 -04001269 az='test-az',
Monty Taylor56f61332017-04-11 05:38:12 -05001270 interface_ip='127.0.0.1',
James E. Blaira38c28e2017-01-04 10:33:20 -08001271 public_ipv4='127.0.0.1',
1272 private_ipv4=None,
1273 public_ipv6=None,
1274 allocated_to=request_id,
1275 state='ready',
1276 state_time=now,
1277 created_time=now,
1278 updated_time=now,
1279 image_id=None,
Paul Belangerc5bf3752017-03-16 19:38:43 -04001280 host_keys=["fake-key1", "fake-key2"],
Paul Belanger174a8272017-03-14 13:20:10 -04001281 executor='fake-nodepool')
James E. Blaira38c28e2017-01-04 10:33:20 -08001282 data = json.dumps(data)
1283 path = self.client.create(path, data,
1284 makepath=True,
1285 sequence=True)
1286 nodeid = path.split("/")[-1]
1287 return nodeid
1288
James E. Blair6ab79e02017-01-06 10:10:17 -08001289 def addFailRequest(self, request):
1290 self.fail_requests.add(request['_oid'])
1291
James E. Blairdce6cea2016-12-20 16:45:32 -08001292 def fulfillRequest(self, request):
James E. Blair6ab79e02017-01-06 10:10:17 -08001293 if request['state'] != 'requested':
James E. Blairdce6cea2016-12-20 16:45:32 -08001294 return
1295 request = request.copy()
James E. Blairdce6cea2016-12-20 16:45:32 -08001296 oid = request['_oid']
1297 del request['_oid']
James E. Blaira38c28e2017-01-04 10:33:20 -08001298
James E. Blair6ab79e02017-01-06 10:10:17 -08001299 if oid in self.fail_requests:
1300 request['state'] = 'failed'
1301 else:
1302 request['state'] = 'fulfilled'
1303 nodes = []
1304 for node in request['node_types']:
1305 nodeid = self.makeNode(oid, node)
1306 nodes.append(nodeid)
1307 request['nodes'] = nodes
James E. Blaira38c28e2017-01-04 10:33:20 -08001308
James E. Blaira38c28e2017-01-04 10:33:20 -08001309 request['state_time'] = time.time()
James E. Blairdce6cea2016-12-20 16:45:32 -08001310 path = self.REQUEST_ROOT + '/' + oid
1311 data = json.dumps(request)
1312 self.log.debug("Fulfilling node request: %s %s" % (oid, data))
1313 self.client.set(path, data)
1314
1315
James E. Blair498059b2016-12-20 13:50:13 -08001316class ChrootedKazooFixture(fixtures.Fixture):
Clark Boylan621ec9a2017-04-07 17:41:33 -07001317 def __init__(self, test_id):
James E. Blair498059b2016-12-20 13:50:13 -08001318 super(ChrootedKazooFixture, self).__init__()
1319
1320 zk_host = os.environ.get('NODEPOOL_ZK_HOST', 'localhost')
1321 if ':' in zk_host:
1322 host, port = zk_host.split(':')
1323 else:
1324 host = zk_host
1325 port = None
1326
1327 self.zookeeper_host = host
1328
1329 if not port:
1330 self.zookeeper_port = 2181
1331 else:
1332 self.zookeeper_port = int(port)
1333
Clark Boylan621ec9a2017-04-07 17:41:33 -07001334 self.test_id = test_id
1335
James E. Blair498059b2016-12-20 13:50:13 -08001336 def _setUp(self):
1337 # Make sure the test chroot paths do not conflict
1338 random_bits = ''.join(random.choice(string.ascii_lowercase +
1339 string.ascii_uppercase)
1340 for x in range(8))
1341
Clark Boylan621ec9a2017-04-07 17:41:33 -07001342 rand_test_path = '%s_%s_%s' % (random_bits, os.getpid(), self.test_id)
James E. Blair498059b2016-12-20 13:50:13 -08001343 self.zookeeper_chroot = "/nodepool_test/%s" % rand_test_path
1344
Clark Boylan7f5f1ec2017-04-07 17:39:56 -07001345 self.addCleanup(self._cleanup)
1346
James E. Blair498059b2016-12-20 13:50:13 -08001347 # Ensure the chroot path exists and clean up any pre-existing znodes.
1348 _tmp_client = kazoo.client.KazooClient(
1349 hosts='%s:%s' % (self.zookeeper_host, self.zookeeper_port))
1350 _tmp_client.start()
1351
1352 if _tmp_client.exists(self.zookeeper_chroot):
1353 _tmp_client.delete(self.zookeeper_chroot, recursive=True)
1354
1355 _tmp_client.ensure_path(self.zookeeper_chroot)
1356 _tmp_client.stop()
1357 _tmp_client.close()
1358
James E. Blair498059b2016-12-20 13:50:13 -08001359 def _cleanup(self):
1360 '''Remove the chroot path.'''
1361 # Need a non-chroot'ed client to remove the chroot path
1362 _tmp_client = kazoo.client.KazooClient(
1363 hosts='%s:%s' % (self.zookeeper_host, self.zookeeper_port))
1364 _tmp_client.start()
1365 _tmp_client.delete(self.zookeeper_chroot, recursive=True)
1366 _tmp_client.stop()
Clark Boylan7f5f1ec2017-04-07 17:39:56 -07001367 _tmp_client.close()
James E. Blair498059b2016-12-20 13:50:13 -08001368
1369
Joshua Heskethd78b4482015-09-14 16:56:34 -06001370class MySQLSchemaFixture(fixtures.Fixture):
1371 def setUp(self):
1372 super(MySQLSchemaFixture, self).setUp()
1373
1374 random_bits = ''.join(random.choice(string.ascii_lowercase +
1375 string.ascii_uppercase)
1376 for x in range(8))
1377 self.name = '%s_%s' % (random_bits, os.getpid())
1378 self.passwd = uuid.uuid4().hex
1379 db = pymysql.connect(host="localhost",
1380 user="openstack_citest",
1381 passwd="openstack_citest",
1382 db="openstack_citest")
1383 cur = db.cursor()
1384 cur.execute("create database %s" % self.name)
1385 cur.execute(
1386 "grant all on %s.* to '%s'@'localhost' identified by '%s'" %
1387 (self.name, self.name, self.passwd))
1388 cur.execute("flush privileges")
1389
1390 self.dburi = 'mysql+pymysql://%s:%s@localhost/%s' % (self.name,
1391 self.passwd,
1392 self.name)
1393 self.addDetail('dburi', testtools.content.text_content(self.dburi))
1394 self.addCleanup(self.cleanup)
1395
1396 def cleanup(self):
1397 db = pymysql.connect(host="localhost",
1398 user="openstack_citest",
1399 passwd="openstack_citest",
1400 db="openstack_citest")
1401 cur = db.cursor()
1402 cur.execute("drop database %s" % self.name)
1403 cur.execute("drop user '%s'@'localhost'" % self.name)
1404 cur.execute("flush privileges")
1405
1406
Maru Newby3fe5f852015-01-13 04:22:14 +00001407class BaseTestCase(testtools.TestCase):
Clark Boylanb640e052014-04-03 16:41:46 -07001408 log = logging.getLogger("zuul.test")
James E. Blair267e5162017-04-07 10:08:20 -07001409 wait_timeout = 30
Clark Boylanb640e052014-04-03 16:41:46 -07001410
James E. Blair1c236df2017-02-01 14:07:24 -08001411 def attachLogs(self, *args):
1412 def reader():
1413 self._log_stream.seek(0)
1414 while True:
1415 x = self._log_stream.read(4096)
1416 if not x:
1417 break
1418 yield x.encode('utf8')
1419 content = testtools.content.content_from_reader(
1420 reader,
1421 testtools.content_type.UTF8_TEXT,
1422 False)
1423 self.addDetail('logging', content)
1424
Clark Boylanb640e052014-04-03 16:41:46 -07001425 def setUp(self):
Maru Newby3fe5f852015-01-13 04:22:14 +00001426 super(BaseTestCase, self).setUp()
Clark Boylanb640e052014-04-03 16:41:46 -07001427 test_timeout = os.environ.get('OS_TEST_TIMEOUT', 0)
1428 try:
1429 test_timeout = int(test_timeout)
1430 except ValueError:
1431 # If timeout value is invalid do not set a timeout.
1432 test_timeout = 0
1433 if test_timeout > 0:
1434 self.useFixture(fixtures.Timeout(test_timeout, gentle=False))
1435
1436 if (os.environ.get('OS_STDOUT_CAPTURE') == 'True' or
1437 os.environ.get('OS_STDOUT_CAPTURE') == '1'):
1438 stdout = self.useFixture(fixtures.StringStream('stdout')).stream
1439 self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout))
1440 if (os.environ.get('OS_STDERR_CAPTURE') == 'True' or
1441 os.environ.get('OS_STDERR_CAPTURE') == '1'):
1442 stderr = self.useFixture(fixtures.StringStream('stderr')).stream
1443 self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr))
1444 if (os.environ.get('OS_LOG_CAPTURE') == 'True' or
1445 os.environ.get('OS_LOG_CAPTURE') == '1'):
James E. Blair1c236df2017-02-01 14:07:24 -08001446 self._log_stream = StringIO()
1447 self.addOnException(self.attachLogs)
1448 else:
1449 self._log_stream = sys.stdout
Maru Newby3fe5f852015-01-13 04:22:14 +00001450
James E. Blair1c236df2017-02-01 14:07:24 -08001451 handler = logging.StreamHandler(self._log_stream)
1452 formatter = logging.Formatter('%(asctime)s %(name)-32s '
1453 '%(levelname)-8s %(message)s')
1454 handler.setFormatter(formatter)
1455
1456 logger = logging.getLogger()
1457 logger.setLevel(logging.DEBUG)
1458 logger.addHandler(handler)
1459
Clark Boylan3410d532017-04-25 12:35:29 -07001460 # Make sure we don't carry old handlers around in process state
1461 # which slows down test runs
1462 self.addCleanup(logger.removeHandler, handler)
1463 self.addCleanup(handler.close)
1464 self.addCleanup(handler.flush)
1465
James E. Blair1c236df2017-02-01 14:07:24 -08001466 # NOTE(notmorgan): Extract logging overrides for specific
1467 # libraries from the OS_LOG_DEFAULTS env and create loggers
1468 # for each. This is used to limit the output during test runs
1469 # from libraries that zuul depends on such as gear.
James E. Blairdce6cea2016-12-20 16:45:32 -08001470 log_defaults_from_env = os.environ.get(
1471 'OS_LOG_DEFAULTS',
James E. Blair1c236df2017-02-01 14:07:24 -08001472 'git.cmd=INFO,kazoo.client=WARNING,gear=INFO')
Morgan Fainbergd34e0b42016-06-09 19:10:38 -07001473
James E. Blairdce6cea2016-12-20 16:45:32 -08001474 if log_defaults_from_env:
1475 for default in log_defaults_from_env.split(','):
1476 try:
1477 name, level_str = default.split('=', 1)
1478 level = getattr(logging, level_str, logging.DEBUG)
James E. Blair1c236df2017-02-01 14:07:24 -08001479 logger = logging.getLogger(name)
1480 logger.setLevel(level)
1481 logger.addHandler(handler)
1482 logger.propagate = False
James E. Blairdce6cea2016-12-20 16:45:32 -08001483 except ValueError:
1484 # NOTE(notmorgan): Invalid format of the log default,
1485 # skip and don't try and apply a logger for the
1486 # specified module
1487 pass
Morgan Fainbergd34e0b42016-06-09 19:10:38 -07001488
Maru Newby3fe5f852015-01-13 04:22:14 +00001489
1490class ZuulTestCase(BaseTestCase):
James E. Blaire7b99a02016-08-05 14:27:34 -07001491 """A test case with a functioning Zuul.
1492
1493 The following class variables are used during test setup and can
1494 be overidden by subclasses but are effectively read-only once a
1495 test method starts running:
1496
1497 :cvar str config_file: This points to the main zuul config file
1498 within the fixtures directory. Subclasses may override this
1499 to obtain a different behavior.
1500
1501 :cvar str tenant_config_file: This is the tenant config file
1502 (which specifies from what git repos the configuration should
1503 be loaded). It defaults to the value specified in
1504 `config_file` but can be overidden by subclasses to obtain a
1505 different tenant/project layout while using the standard main
James E. Blair06cc3922017-04-19 10:08:10 -07001506 configuration. See also the :py:func:`simple_layout`
1507 decorator.
James E. Blaire7b99a02016-08-05 14:27:34 -07001508
Ricardo Carrillo Cruz22994f92016-12-02 11:41:58 +00001509 :cvar bool create_project_keys: Indicates whether Zuul should
1510 auto-generate keys for each project, or whether the test
1511 infrastructure should insert dummy keys to save time during
1512 startup. Defaults to False.
1513
James E. Blaire7b99a02016-08-05 14:27:34 -07001514 The following are instance variables that are useful within test
1515 methods:
1516
1517 :ivar FakeGerritConnection fake_<connection>:
1518 A :py:class:`~tests.base.FakeGerritConnection` will be
1519 instantiated for each connection present in the config file
1520 and stored here. For instance, `fake_gerrit` will hold the
1521 FakeGerritConnection object for a connection named `gerrit`.
1522
1523 :ivar FakeGearmanServer gearman_server: An instance of
1524 :py:class:`~tests.base.FakeGearmanServer` which is the Gearman
1525 server that all of the Zuul components in this test use to
1526 communicate with each other.
1527
Paul Belanger174a8272017-03-14 13:20:10 -04001528 :ivar RecordingExecutorServer executor_server: An instance of
1529 :py:class:`~tests.base.RecordingExecutorServer` which is the
1530 Ansible execute server used to run jobs for this test.
James E. Blaire7b99a02016-08-05 14:27:34 -07001531
1532 :ivar list builds: A list of :py:class:`~tests.base.FakeBuild` objects
1533 representing currently running builds. They are appended to
Paul Belanger174a8272017-03-14 13:20:10 -04001534 the list in the order they are executed, and removed from this
James E. Blaire7b99a02016-08-05 14:27:34 -07001535 list upon completion.
1536
1537 :ivar list history: A list of :py:class:`~tests.base.BuildHistory`
1538 objects representing completed builds. They are appended to
1539 the list in the order they complete.
1540
1541 """
1542
James E. Blair83005782015-12-11 14:46:03 -08001543 config_file = 'zuul.conf'
James E. Blaire1767bc2016-08-02 10:00:27 -07001544 run_ansible = False
Ricardo Carrillo Cruz22994f92016-12-02 11:41:58 +00001545 create_project_keys = False
James E. Blair3f876d52016-07-22 13:07:14 -07001546
1547 def _startMerger(self):
1548 self.merge_server = zuul.merger.server.MergeServer(self.config,
1549 self.connections)
1550 self.merge_server.start()
1551
Maru Newby3fe5f852015-01-13 04:22:14 +00001552 def setUp(self):
1553 super(ZuulTestCase, self).setUp()
James E. Blair498059b2016-12-20 13:50:13 -08001554
1555 self.setupZK()
1556
K Jonathan Harkercc3a6f02017-02-22 19:08:06 -08001557 if not KEEP_TEMPDIRS:
James E. Blair97d902e2014-08-21 13:25:56 -07001558 tmp_root = self.useFixture(fixtures.TempDir(
Joshua Hesketh29d99b72014-08-19 16:27:42 +10001559 rootdir=os.environ.get("ZUUL_TEST_ROOT"))
1560 ).path
James E. Blair97d902e2014-08-21 13:25:56 -07001561 else:
K Jonathan Harkercc3a6f02017-02-22 19:08:06 -08001562 tmp_root = tempfile.mkdtemp(
1563 dir=os.environ.get("ZUUL_TEST_ROOT", None))
Clark Boylanb640e052014-04-03 16:41:46 -07001564 self.test_root = os.path.join(tmp_root, "zuul-test")
1565 self.upstream_root = os.path.join(self.test_root, "upstream")
Monty Taylord642d852017-02-23 14:05:42 -05001566 self.merger_src_root = os.path.join(self.test_root, "merger-git")
Paul Belanger174a8272017-03-14 13:20:10 -04001567 self.executor_src_root = os.path.join(self.test_root, "executor-git")
James E. Blairce8a2132016-05-19 15:21:52 -07001568 self.state_root = os.path.join(self.test_root, "lib")
Clark Boylanb640e052014-04-03 16:41:46 -07001569
1570 if os.path.exists(self.test_root):
1571 shutil.rmtree(self.test_root)
1572 os.makedirs(self.test_root)
1573 os.makedirs(self.upstream_root)
James E. Blairce8a2132016-05-19 15:21:52 -07001574 os.makedirs(self.state_root)
Clark Boylanb640e052014-04-03 16:41:46 -07001575
1576 # Make per test copy of Configuration.
1577 self.setup_config()
James E. Blair59fdbac2015-12-07 17:08:06 -08001578 self.config.set('zuul', 'tenant_config',
Joshua Heskethacccffc2015-03-31 23:38:17 +11001579 os.path.join(FIXTURE_DIR,
James E. Blair59fdbac2015-12-07 17:08:06 -08001580 self.config.get('zuul', 'tenant_config')))
Monty Taylord642d852017-02-23 14:05:42 -05001581 self.config.set('merger', 'git_dir', self.merger_src_root)
Paul Belanger174a8272017-03-14 13:20:10 -04001582 self.config.set('executor', 'git_dir', self.executor_src_root)
James E. Blairce8a2132016-05-19 15:21:52 -07001583 self.config.set('zuul', 'state_dir', self.state_root)
Clark Boylanb640e052014-04-03 16:41:46 -07001584
Clark Boylanb640e052014-04-03 16:41:46 -07001585 self.statsd = FakeStatsd()
Ian Wienandff977bf2015-09-30 15:38:47 +10001586 # note, use 127.0.0.1 rather than localhost to avoid getting ipv6
1587 # see: https://github.com/jsocol/pystatsd/issues/61
1588 os.environ['STATSD_HOST'] = '127.0.0.1'
Clark Boylanb640e052014-04-03 16:41:46 -07001589 os.environ['STATSD_PORT'] = str(self.statsd.port)
1590 self.statsd.start()
1591 # the statsd client object is configured in the statsd module import
Monty Taylor74fa3862016-06-02 07:39:49 +03001592 reload_module(statsd)
1593 reload_module(zuul.scheduler)
Clark Boylanb640e052014-04-03 16:41:46 -07001594
1595 self.gearman_server = FakeGearmanServer()
1596
1597 self.config.set('gearman', 'port', str(self.gearman_server.port))
James E. Blaire47eb772017-02-02 17:19:40 -08001598 self.log.info("Gearman server on port %s" %
1599 (self.gearman_server.port,))
Clark Boylanb640e052014-04-03 16:41:46 -07001600
James E. Blaire511d2f2016-12-08 15:22:26 -08001601 gerritsource.GerritSource.replication_timeout = 1.5
1602 gerritsource.GerritSource.replication_retry_interval = 0.5
1603 gerritconnection.GerritEventConnector.delay = 0.0
Clark Boylanb640e052014-04-03 16:41:46 -07001604
Joshua Hesketh352264b2015-08-11 23:42:08 +10001605 self.sched = zuul.scheduler.Scheduler(self.config)
Clark Boylanb640e052014-04-03 16:41:46 -07001606
Jan Hruban7083edd2015-08-21 14:00:54 +02001607 self.webapp = zuul.webapp.WebApp(
1608 self.sched, port=0, listen_address='127.0.0.1')
1609
Jan Hruban6b71aff2015-10-22 16:58:08 +02001610 self.event_queues = [
1611 self.sched.result_event_queue,
James E. Blair646322f2017-01-27 15:50:34 -08001612 self.sched.trigger_event_queue,
1613 self.sched.management_event_queue
Jan Hruban6b71aff2015-10-22 16:58:08 +02001614 ]
1615
James E. Blairfef78942016-03-11 16:28:56 -08001616 self.configure_connections()
Jan Hruban7083edd2015-08-21 14:00:54 +02001617 self.sched.registerConnections(self.connections, self.webapp)
Joshua Hesketh352264b2015-08-11 23:42:08 +10001618
Clark Boylanb640e052014-04-03 16:41:46 -07001619 def URLOpenerFactory(*args, **kw):
Morgan Fainberg293f7f82016-05-30 14:01:22 -07001620 if isinstance(args[0], urllib.request.Request):
Clark Boylanb640e052014-04-03 16:41:46 -07001621 return old_urlopen(*args, **kw)
Clark Boylanb640e052014-04-03 16:41:46 -07001622 return FakeURLOpener(self.upstream_root, *args, **kw)
1623
Morgan Fainberg293f7f82016-05-30 14:01:22 -07001624 old_urlopen = urllib.request.urlopen
1625 urllib.request.urlopen = URLOpenerFactory
Clark Boylanb640e052014-04-03 16:41:46 -07001626
James E. Blair3f876d52016-07-22 13:07:14 -07001627 self._startMerger()
James E. Blair3f876d52016-07-22 13:07:14 -07001628
Paul Belanger174a8272017-03-14 13:20:10 -04001629 self.executor_server = RecordingExecutorServer(
James E. Blaira92cbc82017-01-23 14:56:49 -08001630 self.config, self.connections,
James E. Blair854f8892017-02-02 11:25:39 -08001631 jobdir_root=self.test_root,
James E. Blaira92cbc82017-01-23 14:56:49 -08001632 _run_ansible=self.run_ansible,
K Jonathan Harkercc3a6f02017-02-22 19:08:06 -08001633 _test_root=self.test_root,
1634 keep_jobdir=KEEP_TEMPDIRS)
Paul Belanger174a8272017-03-14 13:20:10 -04001635 self.executor_server.start()
1636 self.history = self.executor_server.build_history
1637 self.builds = self.executor_server.running_builds
James E. Blaire1767bc2016-08-02 10:00:27 -07001638
Paul Belanger174a8272017-03-14 13:20:10 -04001639 self.executor_client = zuul.executor.client.ExecutorClient(
James E. Blair92e953a2017-03-07 13:08:47 -08001640 self.config, self.sched)
Joshua Hesketh850ccb62014-11-27 11:31:02 +11001641 self.merge_client = zuul.merger.client.MergeClient(
1642 self.config, self.sched)
James E. Blair8d692392016-04-08 17:47:58 -07001643 self.nodepool = zuul.nodepool.Nodepool(self.sched)
James E. Blairdce6cea2016-12-20 16:45:32 -08001644 self.zk = zuul.zk.ZooKeeper()
James E. Blair0d5a36e2017-02-21 10:53:44 -05001645 self.zk.connect(self.zk_config)
James E. Blairdce6cea2016-12-20 16:45:32 -08001646
James E. Blair0d5a36e2017-02-21 10:53:44 -05001647 self.fake_nodepool = FakeNodepool(
1648 self.zk_chroot_fixture.zookeeper_host,
1649 self.zk_chroot_fixture.zookeeper_port,
1650 self.zk_chroot_fixture.zookeeper_chroot)
Clark Boylanb640e052014-04-03 16:41:46 -07001651
Paul Belanger174a8272017-03-14 13:20:10 -04001652 self.sched.setExecutor(self.executor_client)
Clark Boylanb640e052014-04-03 16:41:46 -07001653 self.sched.setMerger(self.merge_client)
James E. Blair8d692392016-04-08 17:47:58 -07001654 self.sched.setNodepool(self.nodepool)
James E. Blairdce6cea2016-12-20 16:45:32 -08001655 self.sched.setZooKeeper(self.zk)
Clark Boylanb640e052014-04-03 16:41:46 -07001656
Joshua Hesketh850ccb62014-11-27 11:31:02 +11001657 self.rpc = zuul.rpclistener.RPCListener(self.config, self.sched)
Clark Boylanb640e052014-04-03 16:41:46 -07001658
1659 self.sched.start()
Clark Boylanb640e052014-04-03 16:41:46 -07001660 self.webapp.start()
1661 self.rpc.start()
Paul Belanger174a8272017-03-14 13:20:10 -04001662 self.executor_client.gearman.waitForServer()
James E. Blaira002b032017-04-18 10:35:48 -07001663 # Cleanups are run in reverse order
1664 self.addCleanup(self.assertCleanShutdown)
Clark Boylanb640e052014-04-03 16:41:46 -07001665 self.addCleanup(self.shutdown)
James E. Blaira002b032017-04-18 10:35:48 -07001666 self.addCleanup(self.assertFinalState)
Clark Boylanb640e052014-04-03 16:41:46 -07001667
James E. Blairb9c0d772017-03-03 14:34:49 -08001668 self.sched.reconfigure(self.config)
1669 self.sched.resume()
1670
James E. Blairfef78942016-03-11 16:28:56 -08001671 def configure_connections(self):
James E. Blaire511d2f2016-12-08 15:22:26 -08001672 # Set up gerrit related fakes
1673 # Set a changes database so multiple FakeGerrit's can report back to
1674 # a virtual canonical database given by the configured hostname
1675 self.gerrit_changes_dbs = {}
1676
1677 def getGerritConnection(driver, name, config):
1678 db = self.gerrit_changes_dbs.setdefault(config['server'], {})
1679 con = FakeGerritConnection(driver, name, config,
1680 changes_db=db,
1681 upstream_root=self.upstream_root)
1682 self.event_queues.append(con.event_queue)
1683 setattr(self, 'fake_' + name, con)
1684 return con
1685
1686 self.useFixture(fixtures.MonkeyPatch(
1687 'zuul.driver.gerrit.GerritDriver.getConnection',
1688 getGerritConnection))
1689
Gregory Haynes4fc12542015-04-22 20:38:06 -07001690 def getGithubConnection(driver, name, config):
1691 con = FakeGithubConnection(driver, name, config,
1692 upstream_root=self.upstream_root)
1693 setattr(self, 'fake_' + name, con)
1694 return con
1695
1696 self.useFixture(fixtures.MonkeyPatch(
1697 'zuul.driver.github.GithubDriver.getConnection',
1698 getGithubConnection))
1699
James E. Blaire511d2f2016-12-08 15:22:26 -08001700 # Set up smtp related fakes
Joshua Heskethd78b4482015-09-14 16:56:34 -06001701 # TODO(jhesketh): This should come from lib.connections for better
1702 # coverage
Joshua Hesketh352264b2015-08-11 23:42:08 +10001703 # Register connections from the config
1704 self.smtp_messages = []
Joshua Hesketh850ccb62014-11-27 11:31:02 +11001705
Joshua Hesketh352264b2015-08-11 23:42:08 +10001706 def FakeSMTPFactory(*args, **kw):
1707 args = [self.smtp_messages] + list(args)
1708 return FakeSMTP(*args, **kw)
Joshua Hesketh850ccb62014-11-27 11:31:02 +11001709
Joshua Hesketh352264b2015-08-11 23:42:08 +10001710 self.useFixture(fixtures.MonkeyPatch('smtplib.SMTP', FakeSMTPFactory))
Joshua Hesketh850ccb62014-11-27 11:31:02 +11001711
James E. Blaire511d2f2016-12-08 15:22:26 -08001712 # Register connections from the config using fakes
James E. Blairfef78942016-03-11 16:28:56 -08001713 self.connections = zuul.lib.connections.ConnectionRegistry()
James E. Blaire511d2f2016-12-08 15:22:26 -08001714 self.connections.configure(self.config)
Joshua Hesketh850ccb62014-11-27 11:31:02 +11001715
James E. Blair83005782015-12-11 14:46:03 -08001716 def setup_config(self):
James E. Blaire7b99a02016-08-05 14:27:34 -07001717 # This creates the per-test configuration object. It can be
1718 # overriden by subclasses, but should not need to be since it
1719 # obeys the config_file and tenant_config_file attributes.
Clark Boylanb640e052014-04-03 16:41:46 -07001720 self.config = ConfigParser.ConfigParser()
James E. Blair83005782015-12-11 14:46:03 -08001721 self.config.read(os.path.join(FIXTURE_DIR, self.config_file))
James E. Blair06cc3922017-04-19 10:08:10 -07001722
1723 if not self.setupSimpleLayout():
1724 if hasattr(self, 'tenant_config_file'):
1725 self.config.set('zuul', 'tenant_config',
1726 self.tenant_config_file)
1727 git_path = os.path.join(
1728 os.path.dirname(
1729 os.path.join(FIXTURE_DIR, self.tenant_config_file)),
1730 'git')
1731 if os.path.exists(git_path):
1732 for reponame in os.listdir(git_path):
1733 project = reponame.replace('_', '/')
1734 self.copyDirToRepo(project,
1735 os.path.join(git_path, reponame))
Ricardo Carrillo Cruz22994f92016-12-02 11:41:58 +00001736 self.setupAllProjectKeys()
1737
James E. Blair06cc3922017-04-19 10:08:10 -07001738 def setupSimpleLayout(self):
1739 # If the test method has been decorated with a simple_layout,
1740 # use that instead of the class tenant_config_file. Set up a
1741 # single config-project with the specified layout, and
1742 # initialize repos for all of the 'project' entries which
1743 # appear in the layout.
1744 test_name = self.id().split('.')[-1]
1745 test = getattr(self, test_name)
1746 if hasattr(test, '__simple_layout__'):
Jesse Keating436a5452017-04-20 11:48:41 -07001747 path, driver = getattr(test, '__simple_layout__')
James E. Blair06cc3922017-04-19 10:08:10 -07001748 else:
1749 return False
1750
James E. Blairb70e55a2017-04-19 12:57:02 -07001751 files = {}
James E. Blair06cc3922017-04-19 10:08:10 -07001752 path = os.path.join(FIXTURE_DIR, path)
1753 with open(path) as f:
James E. Blairb70e55a2017-04-19 12:57:02 -07001754 data = f.read()
1755 layout = yaml.safe_load(data)
1756 files['zuul.yaml'] = data
James E. Blair06cc3922017-04-19 10:08:10 -07001757 untrusted_projects = []
1758 for item in layout:
1759 if 'project' in item:
1760 name = item['project']['name']
1761 untrusted_projects.append(name)
1762 self.init_repo(name)
1763 self.addCommitToRepo(name, 'initial commit',
1764 files={'README': ''},
1765 branch='master', tag='init')
James E. Blairb70e55a2017-04-19 12:57:02 -07001766 if 'job' in item:
1767 jobname = item['job']['name']
1768 files['playbooks/%s.yaml' % jobname] = ''
James E. Blair06cc3922017-04-19 10:08:10 -07001769
1770 root = os.path.join(self.test_root, "config")
1771 if not os.path.exists(root):
1772 os.makedirs(root)
1773 f = tempfile.NamedTemporaryFile(dir=root, delete=False)
1774 config = [{'tenant':
1775 {'name': 'tenant-one',
Jesse Keating436a5452017-04-20 11:48:41 -07001776 'source': {driver:
James E. Blair06cc3922017-04-19 10:08:10 -07001777 {'config-projects': ['common-config'],
1778 'untrusted-projects': untrusted_projects}}}}]
1779 f.write(yaml.dump(config))
1780 f.close()
1781 self.config.set('zuul', 'tenant_config',
1782 os.path.join(FIXTURE_DIR, f.name))
1783
1784 self.init_repo('common-config')
James E. Blair06cc3922017-04-19 10:08:10 -07001785 self.addCommitToRepo('common-config', 'add content from fixture',
1786 files, branch='master', tag='init')
1787
1788 return True
1789
Ricardo Carrillo Cruz22994f92016-12-02 11:41:58 +00001790 def setupAllProjectKeys(self):
1791 if self.create_project_keys:
1792 return
1793
1794 path = self.config.get('zuul', 'tenant_config')
1795 with open(os.path.join(FIXTURE_DIR, path)) as f:
1796 tenant_config = yaml.safe_load(f.read())
1797 for tenant in tenant_config:
1798 sources = tenant['tenant']['source']
1799 for source, conf in sources.items():
James E. Blair109da3f2017-04-04 14:39:43 -07001800 for project in conf.get('config-projects', []):
Ricardo Carrillo Cruz22994f92016-12-02 11:41:58 +00001801 self.setupProjectKeys(source, project)
James E. Blair109da3f2017-04-04 14:39:43 -07001802 for project in conf.get('untrusted-projects', []):
Ricardo Carrillo Cruz22994f92016-12-02 11:41:58 +00001803 self.setupProjectKeys(source, project)
1804
1805 def setupProjectKeys(self, source, project):
1806 # Make sure we set up an RSA key for the project so that we
1807 # don't spend time generating one:
1808
1809 key_root = os.path.join(self.state_root, 'keys')
1810 if not os.path.isdir(key_root):
1811 os.mkdir(key_root, 0o700)
1812 private_key_file = os.path.join(key_root, source, project + '.pem')
1813 private_key_dir = os.path.dirname(private_key_file)
1814 self.log.debug("Installing test keys for project %s at %s" % (
1815 project, private_key_file))
1816 if not os.path.isdir(private_key_dir):
1817 os.makedirs(private_key_dir)
1818 with open(os.path.join(FIXTURE_DIR, 'private.pem')) as i:
1819 with open(private_key_file, 'w') as o:
1820 o.write(i.read())
James E. Blair96c6bf82016-01-15 16:20:40 -08001821
James E. Blair498059b2016-12-20 13:50:13 -08001822 def setupZK(self):
Clark Boylan621ec9a2017-04-07 17:41:33 -07001823 self.zk_chroot_fixture = self.useFixture(
1824 ChrootedKazooFixture(self.id()))
James E. Blair0d5a36e2017-02-21 10:53:44 -05001825 self.zk_config = '%s:%s%s' % (
James E. Blairdce6cea2016-12-20 16:45:32 -08001826 self.zk_chroot_fixture.zookeeper_host,
1827 self.zk_chroot_fixture.zookeeper_port,
1828 self.zk_chroot_fixture.zookeeper_chroot)
James E. Blair498059b2016-12-20 13:50:13 -08001829
James E. Blair96c6bf82016-01-15 16:20:40 -08001830 def copyDirToRepo(self, project, source_path):
James E. Blair8b1dc3f2016-07-05 16:49:00 -07001831 self.init_repo(project)
James E. Blair96c6bf82016-01-15 16:20:40 -08001832
1833 files = {}
1834 for (dirpath, dirnames, filenames) in os.walk(source_path):
1835 for filename in filenames:
1836 test_tree_filepath = os.path.join(dirpath, filename)
1837 common_path = os.path.commonprefix([test_tree_filepath,
1838 source_path])
1839 relative_filepath = test_tree_filepath[len(common_path) + 1:]
1840 with open(test_tree_filepath, 'r') as f:
1841 content = f.read()
1842 files[relative_filepath] = content
1843 self.addCommitToRepo(project, 'add content from fixture',
James E. Blair8b1dc3f2016-07-05 16:49:00 -07001844 files, branch='master', tag='init')
James E. Blair83005782015-12-11 14:46:03 -08001845
James E. Blaire18d4602017-01-05 11:17:28 -08001846 def assertNodepoolState(self):
1847 # Make sure that there are no pending requests
1848
1849 requests = self.fake_nodepool.getNodeRequests()
1850 self.assertEqual(len(requests), 0)
1851
1852 nodes = self.fake_nodepool.getNodes()
1853 for node in nodes:
1854 self.assertFalse(node['_lock'], "Node %s is locked" %
1855 (node['_oid'],))
1856
Ricardo Carrillo Cruz22994f92016-12-02 11:41:58 +00001857 def assertNoGeneratedKeys(self):
1858 # Make sure that Zuul did not generate any project keys
1859 # (unless it was supposed to).
1860
1861 if self.create_project_keys:
1862 return
1863
1864 with open(os.path.join(FIXTURE_DIR, 'private.pem')) as i:
1865 test_key = i.read()
1866
1867 key_root = os.path.join(self.state_root, 'keys')
1868 for root, dirname, files in os.walk(key_root):
1869 for fn in files:
1870 with open(os.path.join(root, fn)) as f:
1871 self.assertEqual(test_key, f.read())
1872
Clark Boylanb640e052014-04-03 16:41:46 -07001873 def assertFinalState(self):
James E. Blaira002b032017-04-18 10:35:48 -07001874 self.log.debug("Assert final state")
1875 # Make sure no jobs are running
1876 self.assertEqual({}, self.executor_server.job_workers)
Clark Boylanb640e052014-04-03 16:41:46 -07001877 # Make sure that git.Repo objects have been garbage collected.
1878 repos = []
1879 gc.collect()
1880 for obj in gc.get_objects():
1881 if isinstance(obj, git.Repo):
James E. Blairc43525f2017-03-03 11:10:26 -08001882 self.log.debug("Leaked git repo object: %s" % repr(obj))
Clark Boylanb640e052014-04-03 16:41:46 -07001883 repos.append(obj)
1884 self.assertEqual(len(repos), 0)
1885 self.assertEmptyQueues()
James E. Blaire18d4602017-01-05 11:17:28 -08001886 self.assertNodepoolState()
Ricardo Carrillo Cruz22994f92016-12-02 11:41:58 +00001887 self.assertNoGeneratedKeys()
James E. Blair83005782015-12-11 14:46:03 -08001888 ipm = zuul.manager.independent.IndependentPipelineManager
James E. Blair59fdbac2015-12-07 17:08:06 -08001889 for tenant in self.sched.abide.tenants.values():
1890 for pipeline in tenant.layout.pipelines.values():
James E. Blair83005782015-12-11 14:46:03 -08001891 if isinstance(pipeline.manager, ipm):
James E. Blair59fdbac2015-12-07 17:08:06 -08001892 self.assertEqual(len(pipeline.queues), 0)
Clark Boylanb640e052014-04-03 16:41:46 -07001893
1894 def shutdown(self):
1895 self.log.debug("Shutting down after tests")
Paul Belanger174a8272017-03-14 13:20:10 -04001896 self.executor_client.stop()
James E. Blair3f876d52016-07-22 13:07:14 -07001897 self.merge_server.stop()
1898 self.merge_server.join()
Clark Boylanb640e052014-04-03 16:41:46 -07001899 self.merge_client.stop()
Paul Belanger174a8272017-03-14 13:20:10 -04001900 self.executor_server.stop()
Clark Boylanb640e052014-04-03 16:41:46 -07001901 self.sched.stop()
1902 self.sched.join()
1903 self.statsd.stop()
1904 self.statsd.join()
1905 self.webapp.stop()
1906 self.webapp.join()
1907 self.rpc.stop()
1908 self.rpc.join()
1909 self.gearman_server.shutdown()
James E. Blairdce6cea2016-12-20 16:45:32 -08001910 self.fake_nodepool.stop()
1911 self.zk.disconnect()
Clark Boylan8208c192017-04-24 18:08:08 -07001912 self.printHistory()
Clark Boylanf18e3b82017-04-24 17:34:13 -07001913 # we whitelist watchdog threads as they have relatively long delays
1914 # before noticing they should exit, but they should exit on their own.
1915 threads = [t for t in threading.enumerate()
1916 if t.name != 'executor-watchdog']
Clark Boylanb640e052014-04-03 16:41:46 -07001917 if len(threads) > 1:
Clark Boylan8208c192017-04-24 18:08:08 -07001918 log_str = ""
1919 for thread_id, stack_frame in sys._current_frames().items():
1920 log_str += "Thread: %s\n" % thread_id
1921 log_str += "".join(traceback.format_stack(stack_frame))
1922 self.log.debug(log_str)
1923 raise Exception("More than one thread is running: %s" % threads)
Clark Boylanb640e052014-04-03 16:41:46 -07001924
James E. Blaira002b032017-04-18 10:35:48 -07001925 def assertCleanShutdown(self):
1926 pass
1927
James E. Blairc4ba97a2017-04-19 16:26:24 -07001928 def init_repo(self, project, tag=None):
Clark Boylanb640e052014-04-03 16:41:46 -07001929 parts = project.split('/')
1930 path = os.path.join(self.upstream_root, *parts[:-1])
1931 if not os.path.exists(path):
1932 os.makedirs(path)
1933 path = os.path.join(self.upstream_root, project)
1934 repo = git.Repo.init(path)
1935
Morgan Fainberg78c301a2016-07-14 13:47:01 -07001936 with repo.config_writer() as config_writer:
1937 config_writer.set_value('user', 'email', 'user@example.com')
1938 config_writer.set_value('user', 'name', 'User Name')
Clark Boylanb640e052014-04-03 16:41:46 -07001939
Clark Boylanb640e052014-04-03 16:41:46 -07001940 repo.index.commit('initial commit')
1941 master = repo.create_head('master')
James E. Blairc4ba97a2017-04-19 16:26:24 -07001942 if tag:
1943 repo.create_tag(tag)
Clark Boylanb640e052014-04-03 16:41:46 -07001944
James E. Blair97d902e2014-08-21 13:25:56 -07001945 repo.head.reference = master
James E. Blair879dafb2015-07-17 14:04:49 -07001946 zuul.merger.merger.reset_repo_to_head(repo)
James E. Blair97d902e2014-08-21 13:25:56 -07001947 repo.git.clean('-x', '-f', '-d')
1948
James E. Blair97d902e2014-08-21 13:25:56 -07001949 def create_branch(self, project, branch):
1950 path = os.path.join(self.upstream_root, project)
1951 repo = git.Repo.init(path)
1952 fn = os.path.join(path, 'README')
1953
1954 branch_head = repo.create_head(branch)
1955 repo.head.reference = branch_head
Clark Boylanb640e052014-04-03 16:41:46 -07001956 f = open(fn, 'a')
James E. Blair97d902e2014-08-21 13:25:56 -07001957 f.write("test %s\n" % branch)
Clark Boylanb640e052014-04-03 16:41:46 -07001958 f.close()
1959 repo.index.add([fn])
James E. Blair97d902e2014-08-21 13:25:56 -07001960 repo.index.commit('%s commit' % branch)
Clark Boylanb640e052014-04-03 16:41:46 -07001961
James E. Blair97d902e2014-08-21 13:25:56 -07001962 repo.head.reference = repo.heads['master']
James E. Blair879dafb2015-07-17 14:04:49 -07001963 zuul.merger.merger.reset_repo_to_head(repo)
Clark Boylanb640e052014-04-03 16:41:46 -07001964 repo.git.clean('-x', '-f', '-d')
1965
Sachi King9f16d522016-03-16 12:20:45 +11001966 def create_commit(self, project):
1967 path = os.path.join(self.upstream_root, project)
1968 repo = git.Repo(path)
1969 repo.head.reference = repo.heads['master']
1970 file_name = os.path.join(path, 'README')
1971 with open(file_name, 'a') as f:
1972 f.write('creating fake commit\n')
1973 repo.index.add([file_name])
1974 commit = repo.index.commit('Creating a fake commit')
1975 return commit.hexsha
1976
James E. Blairf4a5f022017-04-18 14:01:10 -07001977 def orderedRelease(self, count=None):
James E. Blairb8c16472015-05-05 14:55:26 -07001978 # Run one build at a time to ensure non-race order:
James E. Blairf4a5f022017-04-18 14:01:10 -07001979 i = 0
James E. Blairb8c16472015-05-05 14:55:26 -07001980 while len(self.builds):
1981 self.release(self.builds[0])
1982 self.waitUntilSettled()
James E. Blairf4a5f022017-04-18 14:01:10 -07001983 i += 1
1984 if count is not None and i >= count:
1985 break
James E. Blairb8c16472015-05-05 14:55:26 -07001986
Clark Boylanb640e052014-04-03 16:41:46 -07001987 def release(self, job):
1988 if isinstance(job, FakeBuild):
1989 job.release()
1990 else:
1991 job.waiting = False
1992 self.log.debug("Queued job %s released" % job.unique)
1993 self.gearman_server.wakeConnections()
1994
1995 def getParameter(self, job, name):
1996 if isinstance(job, FakeBuild):
1997 return job.parameters[name]
1998 else:
1999 parameters = json.loads(job.arguments)
2000 return parameters[name]
2001
Clark Boylanb640e052014-04-03 16:41:46 -07002002 def haveAllBuildsReported(self):
2003 # See if Zuul is waiting on a meta job to complete
Paul Belanger174a8272017-03-14 13:20:10 -04002004 if self.executor_client.meta_jobs:
Clark Boylanb640e052014-04-03 16:41:46 -07002005 return False
2006 # Find out if every build that the worker has completed has been
2007 # reported back to Zuul. If it hasn't then that means a Gearman
2008 # event is still in transit and the system is not stable.
James E. Blair3f876d52016-07-22 13:07:14 -07002009 for build in self.history:
Paul Belanger174a8272017-03-14 13:20:10 -04002010 zbuild = self.executor_client.builds.get(build.uuid)
Clark Boylanb640e052014-04-03 16:41:46 -07002011 if not zbuild:
2012 # It has already been reported
2013 continue
2014 # It hasn't been reported yet.
2015 return False
2016 # Make sure that none of the worker connections are in GRAB_WAIT
Paul Belanger174a8272017-03-14 13:20:10 -04002017 for connection in self.executor_server.worker.active_connections:
Clark Boylanb640e052014-04-03 16:41:46 -07002018 if connection.state == 'GRAB_WAIT':
2019 return False
2020 return True
2021
2022 def areAllBuildsWaiting(self):
Paul Belanger174a8272017-03-14 13:20:10 -04002023 builds = self.executor_client.builds.values()
James E. Blaira002b032017-04-18 10:35:48 -07002024 seen_builds = set()
Clark Boylanb640e052014-04-03 16:41:46 -07002025 for build in builds:
James E. Blaira002b032017-04-18 10:35:48 -07002026 seen_builds.add(build.uuid)
Clark Boylanb640e052014-04-03 16:41:46 -07002027 client_job = None
Paul Belanger174a8272017-03-14 13:20:10 -04002028 for conn in self.executor_client.gearman.active_connections:
Clark Boylanb640e052014-04-03 16:41:46 -07002029 for j in conn.related_jobs.values():
2030 if j.unique == build.uuid:
2031 client_job = j
2032 break
2033 if not client_job:
2034 self.log.debug("%s is not known to the gearman client" %
2035 build)
James E. Blairf15139b2015-04-02 16:37:15 -07002036 return False
Clark Boylanb640e052014-04-03 16:41:46 -07002037 if not client_job.handle:
2038 self.log.debug("%s has no handle" % client_job)
James E. Blairf15139b2015-04-02 16:37:15 -07002039 return False
Clark Boylanb640e052014-04-03 16:41:46 -07002040 server_job = self.gearman_server.jobs.get(client_job.handle)
2041 if not server_job:
2042 self.log.debug("%s is not known to the gearman server" %
2043 client_job)
James E. Blairf15139b2015-04-02 16:37:15 -07002044 return False
Clark Boylanb640e052014-04-03 16:41:46 -07002045 if not hasattr(server_job, 'waiting'):
2046 self.log.debug("%s is being enqueued" % server_job)
James E. Blairf15139b2015-04-02 16:37:15 -07002047 return False
Clark Boylanb640e052014-04-03 16:41:46 -07002048 if server_job.waiting:
2049 continue
James E. Blair17302972016-08-10 16:11:42 -07002050 if build.url is None:
James E. Blairbbda4702016-03-09 15:19:56 -08002051 self.log.debug("%s has not reported start" % build)
2052 return False
Paul Belanger174a8272017-03-14 13:20:10 -04002053 worker_build = self.executor_server.job_builds.get(
2054 server_job.unique)
James E. Blair962220f2016-08-03 11:22:38 -07002055 if worker_build:
2056 if worker_build.isWaiting():
2057 continue
2058 else:
2059 self.log.debug("%s is running" % worker_build)
2060 return False
Clark Boylanb640e052014-04-03 16:41:46 -07002061 else:
James E. Blair962220f2016-08-03 11:22:38 -07002062 self.log.debug("%s is unassigned" % server_job)
James E. Blairf15139b2015-04-02 16:37:15 -07002063 return False
James E. Blaira002b032017-04-18 10:35:48 -07002064 for (build_uuid, job_worker) in \
2065 self.executor_server.job_workers.items():
2066 if build_uuid not in seen_builds:
2067 self.log.debug("%s is not finalized" % build_uuid)
2068 return False
James E. Blairf15139b2015-04-02 16:37:15 -07002069 return True
Clark Boylanb640e052014-04-03 16:41:46 -07002070
James E. Blairdce6cea2016-12-20 16:45:32 -08002071 def areAllNodeRequestsComplete(self):
James E. Blair15be0e12017-01-03 13:45:20 -08002072 if self.fake_nodepool.paused:
2073 return True
James E. Blairdce6cea2016-12-20 16:45:32 -08002074 if self.sched.nodepool.requests:
2075 return False
2076 return True
2077
Jan Hruban6b71aff2015-10-22 16:58:08 +02002078 def eventQueuesEmpty(self):
2079 for queue in self.event_queues:
2080 yield queue.empty()
2081
2082 def eventQueuesJoin(self):
2083 for queue in self.event_queues:
2084 queue.join()
2085
Clark Boylanb640e052014-04-03 16:41:46 -07002086 def waitUntilSettled(self):
2087 self.log.debug("Waiting until settled...")
2088 start = time.time()
2089 while True:
Clint Byruma9626572017-02-22 14:04:00 -05002090 if time.time() - start > self.wait_timeout:
James E. Blair10fc1eb2016-12-21 16:16:25 -08002091 self.log.error("Timeout waiting for Zuul to settle")
2092 self.log.error("Queue status:")
James E. Blair622c9682016-06-09 08:14:53 -07002093 for queue in self.event_queues:
James E. Blair10fc1eb2016-12-21 16:16:25 -08002094 self.log.error(" %s: %s" % (queue, queue.empty()))
2095 self.log.error("All builds waiting: %s" %
James E. Blair622c9682016-06-09 08:14:53 -07002096 (self.areAllBuildsWaiting(),))
James E. Blair10fc1eb2016-12-21 16:16:25 -08002097 self.log.error("All builds reported: %s" %
James E. Blairf3156c92016-08-10 15:32:19 -07002098 (self.haveAllBuildsReported(),))
James E. Blair10fc1eb2016-12-21 16:16:25 -08002099 self.log.error("All requests completed: %s" %
2100 (self.areAllNodeRequestsComplete(),))
2101 self.log.error("Merge client jobs: %s" %
2102 (self.merge_client.jobs,))
Clark Boylanb640e052014-04-03 16:41:46 -07002103 raise Exception("Timeout waiting for Zuul to settle")
2104 # Make sure no new events show up while we're checking
James E. Blair3f876d52016-07-22 13:07:14 -07002105
Paul Belanger174a8272017-03-14 13:20:10 -04002106 self.executor_server.lock.acquire()
Clark Boylanb640e052014-04-03 16:41:46 -07002107 # have all build states propogated to zuul?
2108 if self.haveAllBuildsReported():
2109 # Join ensures that the queue is empty _and_ events have been
2110 # processed
Jan Hruban6b71aff2015-10-22 16:58:08 +02002111 self.eventQueuesJoin()
Clark Boylanb640e052014-04-03 16:41:46 -07002112 self.sched.run_handler_lock.acquire()
James E. Blair14abdf42015-12-09 16:11:53 -08002113 if (not self.merge_client.jobs and
Clark Boylanb640e052014-04-03 16:41:46 -07002114 self.haveAllBuildsReported() and
James E. Blairdce6cea2016-12-20 16:45:32 -08002115 self.areAllBuildsWaiting() and
James E. Blair36c611a2017-02-06 15:59:43 -08002116 self.areAllNodeRequestsComplete() and
2117 all(self.eventQueuesEmpty())):
2118 # The queue empty check is placed at the end to
2119 # ensure that if a component adds an event between
2120 # when locked the run handler and checked that the
2121 # components were stable, we don't erroneously
2122 # report that we are settled.
Clark Boylanb640e052014-04-03 16:41:46 -07002123 self.sched.run_handler_lock.release()
Paul Belanger174a8272017-03-14 13:20:10 -04002124 self.executor_server.lock.release()
Clark Boylanb640e052014-04-03 16:41:46 -07002125 self.log.debug("...settled.")
2126 return
2127 self.sched.run_handler_lock.release()
Paul Belanger174a8272017-03-14 13:20:10 -04002128 self.executor_server.lock.release()
Clark Boylanb640e052014-04-03 16:41:46 -07002129 self.sched.wake_event.wait(0.1)
2130
2131 def countJobResults(self, jobs, result):
2132 jobs = filter(lambda x: x.result == result, jobs)
2133 return len(jobs)
2134
James E. Blair96c6bf82016-01-15 16:20:40 -08002135 def getJobFromHistory(self, name, project=None):
James E. Blair3f876d52016-07-22 13:07:14 -07002136 for job in self.history:
2137 if (job.name == name and
2138 (project is None or
2139 job.parameters['ZUUL_PROJECT'] == project)):
2140 return job
Clark Boylanb640e052014-04-03 16:41:46 -07002141 raise Exception("Unable to find job %s in history" % name)
2142
2143 def assertEmptyQueues(self):
2144 # Make sure there are no orphaned jobs
James E. Blair59fdbac2015-12-07 17:08:06 -08002145 for tenant in self.sched.abide.tenants.values():
2146 for pipeline in tenant.layout.pipelines.values():
2147 for queue in pipeline.queues:
2148 if len(queue.queue) != 0:
Joshua Hesketh0aa7e8b2016-07-14 00:12:25 +10002149 print('pipeline %s queue %s contents %s' % (
2150 pipeline.name, queue.name, queue.queue))
James E. Blair59fdbac2015-12-07 17:08:06 -08002151 self.assertEqual(len(queue.queue), 0,
2152 "Pipelines queues should be empty")
Clark Boylanb640e052014-04-03 16:41:46 -07002153
2154 def assertReportedStat(self, key, value=None, kind=None):
2155 start = time.time()
2156 while time.time() < (start + 5):
2157 for stat in self.statsd.stats:
Clark Boylanb640e052014-04-03 16:41:46 -07002158 k, v = stat.split(':')
2159 if key == k:
2160 if value is None and kind is None:
2161 return
2162 elif value:
2163 if value == v:
2164 return
2165 elif kind:
2166 if v.endswith('|' + kind):
2167 return
2168 time.sleep(0.1)
2169
Clark Boylanb640e052014-04-03 16:41:46 -07002170 raise Exception("Key %s not found in reported stats" % key)
James E. Blair59fdbac2015-12-07 17:08:06 -08002171
James E. Blair2b2a8ab2016-08-11 14:39:11 -07002172 def assertBuilds(self, builds):
2173 """Assert that the running builds are as described.
2174
2175 The list of running builds is examined and must match exactly
2176 the list of builds described by the input.
2177
2178 :arg list builds: A list of dictionaries. Each item in the
2179 list must match the corresponding build in the build
2180 history, and each element of the dictionary must match the
2181 corresponding attribute of the build.
2182
2183 """
James E. Blair3158e282016-08-19 09:34:11 -07002184 try:
2185 self.assertEqual(len(self.builds), len(builds))
2186 for i, d in enumerate(builds):
2187 for k, v in d.items():
2188 self.assertEqual(
2189 getattr(self.builds[i], k), v,
2190 "Element %i in builds does not match" % (i,))
2191 except Exception:
2192 for build in self.builds:
2193 self.log.error("Running build: %s" % build)
2194 else:
2195 self.log.error("No running builds")
2196 raise
James E. Blair2b2a8ab2016-08-11 14:39:11 -07002197
James E. Blairb536ecc2016-08-31 10:11:42 -07002198 def assertHistory(self, history, ordered=True):
James E. Blair2b2a8ab2016-08-11 14:39:11 -07002199 """Assert that the completed builds are as described.
2200
2201 The list of completed builds is examined and must match
2202 exactly the list of builds described by the input.
2203
2204 :arg list history: A list of dictionaries. Each item in the
2205 list must match the corresponding build in the build
2206 history, and each element of the dictionary must match the
2207 corresponding attribute of the build.
2208
James E. Blairb536ecc2016-08-31 10:11:42 -07002209 :arg bool ordered: If true, the history must match the order
2210 supplied, if false, the builds are permitted to have
2211 arrived in any order.
2212
James E. Blair2b2a8ab2016-08-11 14:39:11 -07002213 """
James E. Blairb536ecc2016-08-31 10:11:42 -07002214 def matches(history_item, item):
2215 for k, v in item.items():
2216 if getattr(history_item, k) != v:
2217 return False
2218 return True
James E. Blair3158e282016-08-19 09:34:11 -07002219 try:
2220 self.assertEqual(len(self.history), len(history))
James E. Blairb536ecc2016-08-31 10:11:42 -07002221 if ordered:
2222 for i, d in enumerate(history):
2223 if not matches(self.history[i], d):
2224 raise Exception(
K Jonathan Harkercc3a6f02017-02-22 19:08:06 -08002225 "Element %i in history does not match %s" %
2226 (i, self.history[i]))
James E. Blairb536ecc2016-08-31 10:11:42 -07002227 else:
2228 unseen = self.history[:]
2229 for i, d in enumerate(history):
2230 found = False
2231 for unseen_item in unseen:
2232 if matches(unseen_item, d):
2233 found = True
2234 unseen.remove(unseen_item)
2235 break
2236 if not found:
2237 raise Exception("No match found for element %i "
2238 "in history" % (i,))
2239 if unseen:
2240 raise Exception("Unexpected items in history")
James E. Blair3158e282016-08-19 09:34:11 -07002241 except Exception:
2242 for build in self.history:
2243 self.log.error("Completed build: %s" % build)
2244 else:
2245 self.log.error("No completed builds")
2246 raise
James E. Blair2b2a8ab2016-08-11 14:39:11 -07002247
James E. Blair6ac368c2016-12-22 18:07:20 -08002248 def printHistory(self):
2249 """Log the build history.
2250
2251 This can be useful during tests to summarize what jobs have
2252 completed.
2253
2254 """
2255 self.log.debug("Build history:")
2256 for build in self.history:
2257 self.log.debug(build)
2258
James E. Blair59fdbac2015-12-07 17:08:06 -08002259 def getPipeline(self, name):
James E. Blairf84026c2015-12-08 16:11:46 -08002260 return self.sched.abide.tenants.values()[0].layout.pipelines.get(name)
2261
James E. Blair9ea70072017-04-19 16:05:30 -07002262 def updateConfigLayout(self, path):
James E. Blairf84026c2015-12-08 16:11:46 -08002263 root = os.path.join(self.test_root, "config")
Clint Byrumead6c562017-02-01 16:34:04 -08002264 if not os.path.exists(root):
2265 os.makedirs(root)
James E. Blairf84026c2015-12-08 16:11:46 -08002266 f = tempfile.NamedTemporaryFile(dir=root, delete=False)
2267 f.write("""
Paul Belanger66e95962016-11-11 12:11:06 -05002268- tenant:
2269 name: openstack
2270 source:
2271 gerrit:
James E. Blair109da3f2017-04-04 14:39:43 -07002272 config-projects:
Paul Belanger66e95962016-11-11 12:11:06 -05002273 - %s
James E. Blair109da3f2017-04-04 14:39:43 -07002274 untrusted-projects:
James E. Blair0ffa0102017-03-30 13:11:33 -07002275 - org/project
2276 - org/project1
James E. Blair7cb84542017-04-19 13:35:05 -07002277 - org/project2\n""" % path)
James E. Blairf84026c2015-12-08 16:11:46 -08002278 f.close()
Paul Belanger66e95962016-11-11 12:11:06 -05002279 self.config.set('zuul', 'tenant_config',
2280 os.path.join(FIXTURE_DIR, f.name))
Ricardo Carrillo Cruz22994f92016-12-02 11:41:58 +00002281 self.setupAllProjectKeys()
James E. Blair14abdf42015-12-09 16:11:53 -08002282
James E. Blair8b1dc3f2016-07-05 16:49:00 -07002283 def addCommitToRepo(self, project, message, files,
2284 branch='master', tag=None):
James E. Blair14abdf42015-12-09 16:11:53 -08002285 path = os.path.join(self.upstream_root, project)
2286 repo = git.Repo(path)
2287 repo.head.reference = branch
2288 zuul.merger.merger.reset_repo_to_head(repo)
2289 for fn, content in files.items():
2290 fn = os.path.join(path, fn)
James E. Blairc73c73a2017-01-20 15:15:15 -08002291 try:
2292 os.makedirs(os.path.dirname(fn))
2293 except OSError:
2294 pass
James E. Blair14abdf42015-12-09 16:11:53 -08002295 with open(fn, 'w') as f:
2296 f.write(content)
2297 repo.index.add([fn])
2298 commit = repo.index.commit(message)
Clint Byrum58264dc2017-02-07 21:21:22 -08002299 before = repo.heads[branch].commit
James E. Blair14abdf42015-12-09 16:11:53 -08002300 repo.heads[branch].commit = commit
2301 repo.head.reference = branch
2302 repo.git.clean('-x', '-f', '-d')
2303 repo.heads[branch].checkout()
James E. Blair8b1dc3f2016-07-05 16:49:00 -07002304 if tag:
2305 repo.create_tag(tag)
Clint Byrum58264dc2017-02-07 21:21:22 -08002306 return before
2307
James E. Blair9ea0d0b2017-04-20 09:27:15 -07002308 def commitConfigUpdate(self, project_name, source_name):
2309 """Commit an update to zuul.yaml
2310
2311 This overwrites the zuul.yaml in the specificed project with
2312 the contents specified.
2313
2314 :arg str project_name: The name of the project containing
2315 zuul.yaml (e.g., common-config)
2316
2317 :arg str source_name: The path to the file (underneath the
2318 test fixture directory) whose contents should be used to
2319 replace zuul.yaml.
2320 """
2321
2322 source_path = os.path.join(FIXTURE_DIR, source_name)
James E. Blairdfdfcfc2017-04-20 10:19:20 -07002323 files = {}
2324 with open(source_path, 'r') as f:
2325 data = f.read()
2326 layout = yaml.safe_load(data)
2327 files['zuul.yaml'] = data
2328 for item in layout:
2329 if 'job' in item:
2330 jobname = item['job']['name']
2331 files['playbooks/%s.yaml' % jobname] = ''
James E. Blair9ea0d0b2017-04-20 09:27:15 -07002332 before = self.addCommitToRepo(
2333 project_name, 'Pulling content from %s' % source_name,
James E. Blairdfdfcfc2017-04-20 10:19:20 -07002334 files)
James E. Blair9ea0d0b2017-04-20 09:27:15 -07002335 return before
2336
James E. Blair7fc8daa2016-08-08 15:37:15 -07002337 def addEvent(self, connection, event):
James E. Blair9ea0d0b2017-04-20 09:27:15 -07002338
James E. Blair7fc8daa2016-08-08 15:37:15 -07002339 """Inject a Fake (Gerrit) event.
2340
2341 This method accepts a JSON-encoded event and simulates Zuul
2342 having received it from Gerrit. It could (and should)
2343 eventually apply to any connection type, but is currently only
2344 used with Gerrit connections. The name of the connection is
2345 used to look up the corresponding server, and the event is
2346 simulated as having been received by all Zuul connections
2347 attached to that server. So if two Gerrit connections in Zuul
2348 are connected to the same Gerrit server, and you invoke this
2349 method specifying the name of one of them, the event will be
2350 received by both.
2351
2352 .. note::
2353
2354 "self.fake_gerrit.addEvent" calls should be migrated to
2355 this method.
2356
2357 :arg str connection: The name of the connection corresponding
Clark Boylan500992b2017-04-03 14:28:24 -07002358 to the gerrit server.
James E. Blair7fc8daa2016-08-08 15:37:15 -07002359 :arg str event: The JSON-encoded event.
2360
2361 """
2362 specified_conn = self.connections.connections[connection]
2363 for conn in self.connections.connections.values():
2364 if (isinstance(conn, specified_conn.__class__) and
2365 specified_conn.server == conn.server):
2366 conn.addEvent(event)
2367
James E. Blair3f876d52016-07-22 13:07:14 -07002368
2369class AnsibleZuulTestCase(ZuulTestCase):
Paul Belanger174a8272017-03-14 13:20:10 -04002370 """ZuulTestCase but with an actual ansible executor running"""
James E. Blaire1767bc2016-08-02 10:00:27 -07002371 run_ansible = True
Joshua Hesketh25695cb2017-03-06 12:50:04 +11002372
Joshua Heskethd78b4482015-09-14 16:56:34 -06002373
2374class ZuulDBTestCase(ZuulTestCase):
James E. Blair82844892017-03-06 10:55:26 -08002375 def setup_config(self):
2376 super(ZuulDBTestCase, self).setup_config()
Joshua Heskethd78b4482015-09-14 16:56:34 -06002377 for section_name in self.config.sections():
2378 con_match = re.match(r'^connection ([\'\"]?)(.*)(\1)$',
2379 section_name, re.I)
2380 if not con_match:
2381 continue
2382
2383 if self.config.get(section_name, 'driver') == 'sql':
2384 f = MySQLSchemaFixture()
2385 self.useFixture(f)
2386 if (self.config.get(section_name, 'dburi') ==
2387 '$MYSQL_FIXTURE_DBURI$'):
2388 self.config.set(section_name, 'dburi', f.dburi)