blob: 56b1269540342bb214eea296ac96ea957d409640 [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 = []
559 self.updated_at = None
560 self.head_sha = None
561 self._createPRRef()
562 self._addCommitToRepo()
563 self._updateTimeStamp()
564
565 def addCommit(self):
566 """Adds a commit on top of the actual PR head."""
567 self._addCommitToRepo()
568 self._updateTimeStamp()
569
570 def forcePush(self):
571 """Clears actual commits and add a commit on top of the base."""
572 self._addCommitToRepo(reset=True)
573 self._updateTimeStamp()
574
575 def getPullRequestOpenedEvent(self):
576 return self._getPullRequestEvent('opened')
577
578 def getPullRequestSynchronizeEvent(self):
579 return self._getPullRequestEvent('synchronize')
580
581 def getPullRequestReopenedEvent(self):
582 return self._getPullRequestEvent('reopened')
583
584 def getPullRequestClosedEvent(self):
585 return self._getPullRequestEvent('closed')
586
587 def addComment(self, message):
588 self.comments.append(message)
589 self._updateTimeStamp()
590
Jan Hrubanc7ab1602015-10-14 15:29:33 +0200591 def getCommentAddedEvent(self, text):
592 name = 'issue_comment'
593 data = {
594 'action': 'created',
595 'issue': {
596 'number': self.number
597 },
598 'comment': {
599 'body': text
600 },
601 'repository': {
602 'full_name': self.project
603 }
604 }
605 return (name, data)
606
Gregory Haynes4fc12542015-04-22 20:38:06 -0700607 def _getRepo(self):
608 repo_path = os.path.join(self.upstream_root, self.project)
609 return git.Repo(repo_path)
610
611 def _createPRRef(self):
612 repo = self._getRepo()
613 GithubChangeReference.create(
614 repo, self._getPRReference(), 'refs/tags/init')
615
616 def _addCommitToRepo(self, reset=False):
617 repo = self._getRepo()
618 ref = repo.references[self._getPRReference()]
619 if reset:
620 ref.set_object('refs/tags/init')
621 repo.head.reference = ref
622 zuul.merger.merger.reset_repo_to_head(repo)
623 repo.git.clean('-x', '-f', '-d')
624
625 fn = '%s-%s' % (self.branch.replace('/', '_'), self.number)
626 msg = 'test-%s' % self.number
627 fn = os.path.join(repo.working_dir, fn)
628 f = open(fn, 'w')
629 with open(fn, 'w') as f:
630 f.write("test %s %s\n" %
631 (self.branch, self.number))
632 repo.index.add([fn])
633
634 self.head_sha = repo.index.commit(msg).hexsha
635 repo.head.reference = 'master'
636 zuul.merger.merger.reset_repo_to_head(repo)
637 repo.git.clean('-x', '-f', '-d')
638 repo.heads['master'].checkout()
639
640 def _updateTimeStamp(self):
641 self.updated_at = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.localtime())
642
643 def getPRHeadSha(self):
644 repo = self._getRepo()
645 return repo.references[self._getPRReference()].commit.hexsha
646
647 def _getPRReference(self):
648 return '%s/head' % self.number
649
650 def _getPullRequestEvent(self, action):
651 name = 'pull_request'
652 data = {
653 'action': action,
654 'number': self.number,
655 'pull_request': {
656 'number': self.number,
657 'updated_at': self.updated_at,
658 'base': {
659 'ref': self.branch,
660 'repo': {
661 'full_name': self.project
662 }
663 },
664 'head': {
665 'sha': self.head_sha
666 }
667 }
668 }
669 return (name, data)
670
671
672class FakeGithubConnection(githubconnection.GithubConnection):
673 log = logging.getLogger("zuul.test.FakeGithubConnection")
674
675 def __init__(self, driver, connection_name, connection_config,
676 upstream_root=None):
677 super(FakeGithubConnection, self).__init__(driver, connection_name,
678 connection_config)
679 self.connection_name = connection_name
680 self.pr_number = 0
681 self.pull_requests = []
682 self.upstream_root = upstream_root
683
684 def openFakePullRequest(self, project, branch):
685 self.pr_number += 1
686 pull_request = FakeGithubPullRequest(
687 self, self.pr_number, project, branch, self.upstream_root)
688 self.pull_requests.append(pull_request)
689 return pull_request
690
Wayne1a78c612015-06-11 17:14:13 -0700691 def getPushEvent(self, project, ref, old_rev=None, new_rev=None):
692 if not old_rev:
693 old_rev = '00000000000000000000000000000000'
694 if not new_rev:
695 new_rev = random_sha1()
696 name = 'push'
697 data = {
698 'ref': ref,
699 'before': old_rev,
700 'after': new_rev,
701 'repository': {
702 'full_name': project
703 }
704 }
705 return (name, data)
706
Gregory Haynes4fc12542015-04-22 20:38:06 -0700707 def emitEvent(self, event):
708 """Emulates sending the GitHub webhook event to the connection."""
709 port = self.webapp.server.socket.getsockname()[1]
710 name, data = event
711 payload = json.dumps(data)
712 headers = {'X-Github-Event': name}
713 req = urllib.request.Request(
714 'http://localhost:%s/connection/%s/payload'
715 % (port, self.connection_name),
716 data=payload, headers=headers)
717 urllib.request.urlopen(req)
718
Jan Hrubanc7ab1602015-10-14 15:29:33 +0200719 def getPull(self, project, number):
720 pr = self.pull_requests[number - 1]
721 data = {
722 'number': number,
723 'updated_at': pr.updated_at,
724 'base': {
725 'repo': {
726 'full_name': pr.project
727 },
728 'ref': pr.branch,
729 },
730 'head': {
731 'sha': pr.head_sha
732 }
733 }
734 return data
735
Gregory Haynes4fc12542015-04-22 20:38:06 -0700736 def getGitUrl(self, project):
737 return os.path.join(self.upstream_root, str(project))
738
Jan Hruban6d53c5e2015-10-24 03:03:34 +0200739 def real_getGitUrl(self, project):
740 return super(FakeGithubConnection, self).getGitUrl(project)
741
Gregory Haynes4fc12542015-04-22 20:38:06 -0700742 def getProjectBranches(self, project):
743 """Masks getProjectBranches since we don't have a real github"""
744
745 # just returns master for now
746 return ['master']
747
Wayne40f40042015-06-12 16:56:30 -0700748 def report(self, project, pr_number, message, params=None):
749 pull_request = self.pull_requests[pr_number - 1]
750 pull_request.addComment(message)
751
Gregory Haynes4fc12542015-04-22 20:38:06 -0700752
Clark Boylanb640e052014-04-03 16:41:46 -0700753class BuildHistory(object):
754 def __init__(self, **kw):
755 self.__dict__.update(kw)
756
757 def __repr__(self):
James E. Blair17302972016-08-10 16:11:42 -0700758 return ("<Completed build, result: %s name: %s uuid: %s changes: %s>" %
759 (self.result, self.name, self.uuid, self.changes))
Clark Boylanb640e052014-04-03 16:41:46 -0700760
761
762class FakeURLOpener(object):
Jan Hruban6b71aff2015-10-22 16:58:08 +0200763 def __init__(self, upstream_root, url):
Clark Boylanb640e052014-04-03 16:41:46 -0700764 self.upstream_root = upstream_root
Clark Boylanb640e052014-04-03 16:41:46 -0700765 self.url = url
766
767 def read(self):
Morgan Fainberg293f7f82016-05-30 14:01:22 -0700768 res = urllib.parse.urlparse(self.url)
Clark Boylanb640e052014-04-03 16:41:46 -0700769 path = res.path
770 project = '/'.join(path.split('/')[2:-2])
771 ret = '001e# service=git-upload-pack\n'
772 ret += ('000000a31270149696713ba7e06f1beb760f20d359c4abed HEAD\x00'
773 'multi_ack thin-pack side-band side-band-64k ofs-delta '
774 'shallow no-progress include-tag multi_ack_detailed no-done\n')
775 path = os.path.join(self.upstream_root, project)
776 repo = git.Repo(path)
777 for ref in repo.refs:
778 r = ref.object.hexsha + ' ' + ref.path + '\n'
779 ret += '%04x%s' % (len(r) + 4, r)
780 ret += '0000'
781 return ret
782
783
Clark Boylanb640e052014-04-03 16:41:46 -0700784class FakeStatsd(threading.Thread):
785 def __init__(self):
786 threading.Thread.__init__(self)
787 self.daemon = True
788 self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
789 self.sock.bind(('', 0))
790 self.port = self.sock.getsockname()[1]
791 self.wake_read, self.wake_write = os.pipe()
792 self.stats = []
793
794 def run(self):
795 while True:
796 poll = select.poll()
797 poll.register(self.sock, select.POLLIN)
798 poll.register(self.wake_read, select.POLLIN)
799 ret = poll.poll()
800 for (fd, event) in ret:
801 if fd == self.sock.fileno():
802 data = self.sock.recvfrom(1024)
803 if not data:
804 return
805 self.stats.append(data[0])
806 if fd == self.wake_read:
807 return
808
809 def stop(self):
810 os.write(self.wake_write, '1\n')
811
812
James E. Blaire1767bc2016-08-02 10:00:27 -0700813class FakeBuild(object):
Clark Boylanb640e052014-04-03 16:41:46 -0700814 log = logging.getLogger("zuul.test")
815
Paul Belanger174a8272017-03-14 13:20:10 -0400816 def __init__(self, executor_server, job):
Clark Boylanb640e052014-04-03 16:41:46 -0700817 self.daemon = True
Paul Belanger174a8272017-03-14 13:20:10 -0400818 self.executor_server = executor_server
Clark Boylanb640e052014-04-03 16:41:46 -0700819 self.job = job
James E. Blairab7132b2016-08-05 12:36:22 -0700820 self.jobdir = None
James E. Blair17302972016-08-10 16:11:42 -0700821 self.uuid = job.unique
Clark Boylanb640e052014-04-03 16:41:46 -0700822 self.parameters = json.loads(job.arguments)
James E. Blair34776ee2016-08-25 13:53:54 -0700823 # TODOv3(jeblair): self.node is really "the image of the node
824 # assigned". We should rename it (self.node_image?) if we
825 # keep using it like this, or we may end up exposing more of
826 # the complexity around multi-node jobs here
827 # (self.nodes[0].image?)
828 self.node = None
829 if len(self.parameters.get('nodes')) == 1:
830 self.node = self.parameters['nodes'][0]['image']
Clark Boylanb640e052014-04-03 16:41:46 -0700831 self.unique = self.parameters['ZUUL_UUID']
Jamie Lennoxd2e37332016-12-05 15:26:19 +1100832 self.pipeline = self.parameters['ZUUL_PIPELINE']
833 self.project = self.parameters['ZUUL_PROJECT']
James E. Blair3f876d52016-07-22 13:07:14 -0700834 self.name = self.parameters['job']
Clark Boylanb640e052014-04-03 16:41:46 -0700835 self.wait_condition = threading.Condition()
836 self.waiting = False
837 self.aborted = False
Paul Belanger71d98172016-11-08 10:56:31 -0500838 self.requeue = False
Clark Boylanb640e052014-04-03 16:41:46 -0700839 self.created = time.time()
James E. Blaire1767bc2016-08-02 10:00:27 -0700840 self.changes = None
841 if 'ZUUL_CHANGE_IDS' in self.parameters:
842 self.changes = self.parameters['ZUUL_CHANGE_IDS']
Clark Boylanb640e052014-04-03 16:41:46 -0700843
James E. Blair3158e282016-08-19 09:34:11 -0700844 def __repr__(self):
845 waiting = ''
846 if self.waiting:
847 waiting = ' [waiting]'
Jamie Lennoxd2e37332016-12-05 15:26:19 +1100848 return '<FakeBuild %s:%s %s%s>' % (self.pipeline, self.name,
849 self.changes, waiting)
James E. Blair3158e282016-08-19 09:34:11 -0700850
Clark Boylanb640e052014-04-03 16:41:46 -0700851 def release(self):
James E. Blaire7b99a02016-08-05 14:27:34 -0700852 """Release this build."""
Clark Boylanb640e052014-04-03 16:41:46 -0700853 self.wait_condition.acquire()
854 self.wait_condition.notify()
855 self.waiting = False
856 self.log.debug("Build %s released" % self.unique)
857 self.wait_condition.release()
858
859 def isWaiting(self):
James E. Blaire7b99a02016-08-05 14:27:34 -0700860 """Return whether this build is being held.
861
862 :returns: Whether the build is being held.
863 :rtype: bool
864 """
865
Clark Boylanb640e052014-04-03 16:41:46 -0700866 self.wait_condition.acquire()
867 if self.waiting:
868 ret = True
869 else:
870 ret = False
871 self.wait_condition.release()
872 return ret
873
874 def _wait(self):
875 self.wait_condition.acquire()
876 self.waiting = True
877 self.log.debug("Build %s waiting" % self.unique)
878 self.wait_condition.wait()
879 self.wait_condition.release()
880
881 def run(self):
Clark Boylanb640e052014-04-03 16:41:46 -0700882 self.log.debug('Running build %s' % self.unique)
883
Paul Belanger174a8272017-03-14 13:20:10 -0400884 if self.executor_server.hold_jobs_in_build:
Clark Boylanb640e052014-04-03 16:41:46 -0700885 self.log.debug('Holding build %s' % self.unique)
886 self._wait()
887 self.log.debug("Build %s continuing" % self.unique)
888
James E. Blair412fba82017-01-26 15:00:50 -0800889 result = (RecordingAnsibleJob.RESULT_NORMAL, 0) # Success
James E. Blaira5dba232016-08-08 15:53:24 -0700890 if (('ZUUL_REF' in self.parameters) and self.shouldFail()):
James E. Blair412fba82017-01-26 15:00:50 -0800891 result = (RecordingAnsibleJob.RESULT_NORMAL, 1) # Failure
Clark Boylanb640e052014-04-03 16:41:46 -0700892 if self.aborted:
James E. Blair412fba82017-01-26 15:00:50 -0800893 result = (RecordingAnsibleJob.RESULT_ABORTED, None)
Paul Belanger71d98172016-11-08 10:56:31 -0500894 if self.requeue:
James E. Blair412fba82017-01-26 15:00:50 -0800895 result = (RecordingAnsibleJob.RESULT_UNREACHABLE, None)
Clark Boylanb640e052014-04-03 16:41:46 -0700896
James E. Blaire1767bc2016-08-02 10:00:27 -0700897 return result
Clark Boylanb640e052014-04-03 16:41:46 -0700898
James E. Blaira5dba232016-08-08 15:53:24 -0700899 def shouldFail(self):
Paul Belanger174a8272017-03-14 13:20:10 -0400900 changes = self.executor_server.fail_tests.get(self.name, [])
James E. Blaira5dba232016-08-08 15:53:24 -0700901 for change in changes:
902 if self.hasChanges(change):
903 return True
904 return False
905
James E. Blaire7b99a02016-08-05 14:27:34 -0700906 def hasChanges(self, *changes):
907 """Return whether this build has certain changes in its git repos.
908
909 :arg FakeChange changes: One or more changes (varargs) that
Clark Boylan500992b2017-04-03 14:28:24 -0700910 are expected to be present (in order) in the git repository of
911 the active project.
James E. Blaire7b99a02016-08-05 14:27:34 -0700912
913 :returns: Whether the build has the indicated changes.
914 :rtype: bool
915
916 """
Clint Byrum3343e3e2016-11-15 16:05:03 -0800917 for change in changes:
Gregory Haynes4fc12542015-04-22 20:38:06 -0700918 hostname = change.source.canonical_hostname
James E. Blair2a535672017-04-27 12:03:15 -0700919 path = os.path.join(self.jobdir.src_root, hostname, change.project)
Clint Byrum3343e3e2016-11-15 16:05:03 -0800920 try:
921 repo = git.Repo(path)
922 except NoSuchPathError as e:
923 self.log.debug('%s' % e)
924 return False
925 ref = self.parameters['ZUUL_REF']
926 repo_messages = [c.message.strip() for c in repo.iter_commits(ref)]
927 commit_message = '%s-1' % change.subject
928 self.log.debug("Checking if build %s has changes; commit_message "
929 "%s; repo_messages %s" % (self, commit_message,
930 repo_messages))
931 if commit_message not in repo_messages:
James E. Blair962220f2016-08-03 11:22:38 -0700932 self.log.debug(" messages do not match")
933 return False
934 self.log.debug(" OK")
935 return True
936
Clark Boylanb640e052014-04-03 16:41:46 -0700937
Paul Belanger174a8272017-03-14 13:20:10 -0400938class RecordingExecutorServer(zuul.executor.server.ExecutorServer):
939 """An Ansible executor to be used in tests.
James E. Blaire7b99a02016-08-05 14:27:34 -0700940
Paul Belanger174a8272017-03-14 13:20:10 -0400941 :ivar bool hold_jobs_in_build: If true, when jobs are executed
James E. Blaire7b99a02016-08-05 14:27:34 -0700942 they will report that they have started but then pause until
943 released before reporting completion. This attribute may be
944 changed at any time and will take effect for subsequently
Paul Belanger174a8272017-03-14 13:20:10 -0400945 executed builds, but previously held builds will still need to
James E. Blaire7b99a02016-08-05 14:27:34 -0700946 be explicitly released.
947
948 """
James E. Blairf5dbd002015-12-23 15:26:17 -0800949 def __init__(self, *args, **kw):
James E. Blaire1767bc2016-08-02 10:00:27 -0700950 self._run_ansible = kw.pop('_run_ansible', False)
James E. Blaira92cbc82017-01-23 14:56:49 -0800951 self._test_root = kw.pop('_test_root', False)
Paul Belanger174a8272017-03-14 13:20:10 -0400952 super(RecordingExecutorServer, self).__init__(*args, **kw)
James E. Blaire1767bc2016-08-02 10:00:27 -0700953 self.hold_jobs_in_build = False
954 self.lock = threading.Lock()
955 self.running_builds = []
James E. Blair3f876d52016-07-22 13:07:14 -0700956 self.build_history = []
James E. Blaire1767bc2016-08-02 10:00:27 -0700957 self.fail_tests = {}
James E. Blairab7132b2016-08-05 12:36:22 -0700958 self.job_builds = {}
James E. Blairf5dbd002015-12-23 15:26:17 -0800959
James E. Blaira5dba232016-08-08 15:53:24 -0700960 def failJob(self, name, change):
Paul Belanger174a8272017-03-14 13:20:10 -0400961 """Instruct the executor to report matching builds as failures.
James E. Blaire7b99a02016-08-05 14:27:34 -0700962
963 :arg str name: The name of the job to fail.
James E. Blaira5dba232016-08-08 15:53:24 -0700964 :arg Change change: The :py:class:`~tests.base.FakeChange`
965 instance which should cause the job to fail. This job
966 will also fail for changes depending on this change.
James E. Blaire7b99a02016-08-05 14:27:34 -0700967
968 """
James E. Blaire1767bc2016-08-02 10:00:27 -0700969 l = self.fail_tests.get(name, [])
970 l.append(change)
971 self.fail_tests[name] = l
James E. Blairf5dbd002015-12-23 15:26:17 -0800972
James E. Blair962220f2016-08-03 11:22:38 -0700973 def release(self, regex=None):
James E. Blaire7b99a02016-08-05 14:27:34 -0700974 """Release a held build.
975
976 :arg str regex: A regular expression which, if supplied, will
977 cause only builds with matching names to be released. If
978 not supplied, all builds will be released.
979
980 """
James E. Blair962220f2016-08-03 11:22:38 -0700981 builds = self.running_builds[:]
982 self.log.debug("Releasing build %s (%s)" % (regex,
983 len(self.running_builds)))
984 for build in builds:
985 if not regex or re.match(regex, build.name):
986 self.log.debug("Releasing build %s" %
987 (build.parameters['ZUUL_UUID']))
988 build.release()
989 else:
990 self.log.debug("Not releasing build %s" %
991 (build.parameters['ZUUL_UUID']))
992 self.log.debug("Done releasing builds %s (%s)" %
993 (regex, len(self.running_builds)))
994
Paul Belanger174a8272017-03-14 13:20:10 -0400995 def executeJob(self, job):
James E. Blair34776ee2016-08-25 13:53:54 -0700996 build = FakeBuild(self, job)
James E. Blaire1767bc2016-08-02 10:00:27 -0700997 job.build = build
James E. Blaire1767bc2016-08-02 10:00:27 -0700998 self.running_builds.append(build)
James E. Blairab7132b2016-08-05 12:36:22 -0700999 self.job_builds[job.unique] = build
James E. Blaira92cbc82017-01-23 14:56:49 -08001000 args = json.loads(job.arguments)
James E. Blair490cf042017-02-24 23:07:21 -05001001 args['vars']['zuul']['_test'] = dict(test_root=self._test_root)
James E. Blaira92cbc82017-01-23 14:56:49 -08001002 job.arguments = json.dumps(args)
Joshua Hesketh50c21782016-10-13 21:34:14 +11001003 self.job_workers[job.unique] = RecordingAnsibleJob(self, job)
1004 self.job_workers[job.unique].run()
James E. Blair17302972016-08-10 16:11:42 -07001005
1006 def stopJob(self, job):
1007 self.log.debug("handle stop")
1008 parameters = json.loads(job.arguments)
1009 uuid = parameters['uuid']
1010 for build in self.running_builds:
1011 if build.unique == uuid:
1012 build.aborted = True
1013 build.release()
Paul Belanger174a8272017-03-14 13:20:10 -04001014 super(RecordingExecutorServer, self).stopJob(job)
James E. Blairab7132b2016-08-05 12:36:22 -07001015
James E. Blaira002b032017-04-18 10:35:48 -07001016 def stop(self):
1017 for build in self.running_builds:
1018 build.release()
1019 super(RecordingExecutorServer, self).stop()
1020
Joshua Hesketh50c21782016-10-13 21:34:14 +11001021
Paul Belanger174a8272017-03-14 13:20:10 -04001022class RecordingAnsibleJob(zuul.executor.server.AnsibleJob):
K Jonathan Harkerae04e4c2017-03-15 19:07:11 -07001023 def doMergeChanges(self, items):
1024 # Get a merger in order to update the repos involved in this job.
1025 commit = super(RecordingAnsibleJob, self).doMergeChanges(items)
1026 if not commit: # merge conflict
1027 self.recordResult('MERGER_FAILURE')
1028 return commit
1029
1030 def recordResult(self, result):
Paul Belanger174a8272017-03-14 13:20:10 -04001031 build = self.executor_server.job_builds[self.job.unique]
Paul Belanger174a8272017-03-14 13:20:10 -04001032 self.executor_server.lock.acquire()
1033 self.executor_server.build_history.append(
James E. Blair17302972016-08-10 16:11:42 -07001034 BuildHistory(name=build.name, result=result, changes=build.changes,
1035 node=build.node, uuid=build.unique,
K Jonathan Harker2c1a6232017-02-21 14:34:08 -08001036 parameters=build.parameters, jobdir=build.jobdir,
James E. Blaire1767bc2016-08-02 10:00:27 -07001037 pipeline=build.parameters['ZUUL_PIPELINE'])
1038 )
Paul Belanger174a8272017-03-14 13:20:10 -04001039 self.executor_server.running_builds.remove(build)
1040 del self.executor_server.job_builds[self.job.unique]
1041 self.executor_server.lock.release()
K Jonathan Harkerae04e4c2017-03-15 19:07:11 -07001042
1043 def runPlaybooks(self, args):
1044 build = self.executor_server.job_builds[self.job.unique]
1045 build.jobdir = self.jobdir
1046
1047 result = super(RecordingAnsibleJob, self).runPlaybooks(args)
1048 self.recordResult(result)
James E. Blair412fba82017-01-26 15:00:50 -08001049 return result
1050
Monty Taylore6562aa2017-02-20 07:37:39 -05001051 def runAnsible(self, cmd, timeout, trusted=False):
Paul Belanger174a8272017-03-14 13:20:10 -04001052 build = self.executor_server.job_builds[self.job.unique]
James E. Blair412fba82017-01-26 15:00:50 -08001053
Paul Belanger174a8272017-03-14 13:20:10 -04001054 if self.executor_server._run_ansible:
Monty Taylorc231d932017-02-03 09:57:15 -06001055 result = super(RecordingAnsibleJob, self).runAnsible(
Monty Taylore6562aa2017-02-20 07:37:39 -05001056 cmd, timeout, trusted=trusted)
James E. Blair412fba82017-01-26 15:00:50 -08001057 else:
1058 result = build.run()
James E. Blaire1767bc2016-08-02 10:00:27 -07001059 return result
James E. Blairf5dbd002015-12-23 15:26:17 -08001060
James E. Blairad8dca02017-02-21 11:48:32 -05001061 def getHostList(self, args):
1062 self.log.debug("hostlist")
1063 hosts = super(RecordingAnsibleJob, self).getHostList(args)
Paul Belangerc5bf3752017-03-16 19:38:43 -04001064 for host in hosts:
1065 host['host_vars']['ansible_connection'] = 'local'
1066
1067 hosts.append(dict(
1068 name='localhost',
1069 host_vars=dict(ansible_connection='local'),
1070 host_keys=[]))
James E. Blairad8dca02017-02-21 11:48:32 -05001071 return hosts
1072
James E. Blairf5dbd002015-12-23 15:26:17 -08001073
Clark Boylanb640e052014-04-03 16:41:46 -07001074class FakeGearmanServer(gear.Server):
James E. Blaire7b99a02016-08-05 14:27:34 -07001075 """A Gearman server for use in tests.
1076
1077 :ivar bool hold_jobs_in_queue: If true, submitted jobs will be
1078 added to the queue but will not be distributed to workers
1079 until released. This attribute may be changed at any time and
1080 will take effect for subsequently enqueued jobs, but
1081 previously held jobs will still need to be explicitly
1082 released.
1083
1084 """
1085
Clark Boylanb640e052014-04-03 16:41:46 -07001086 def __init__(self):
1087 self.hold_jobs_in_queue = False
1088 super(FakeGearmanServer, self).__init__(0)
1089
1090 def getJobForConnection(self, connection, peek=False):
1091 for queue in [self.high_queue, self.normal_queue, self.low_queue]:
1092 for job in queue:
1093 if not hasattr(job, 'waiting'):
Paul Belanger174a8272017-03-14 13:20:10 -04001094 if job.name.startswith('executor:execute'):
Clark Boylanb640e052014-04-03 16:41:46 -07001095 job.waiting = self.hold_jobs_in_queue
1096 else:
1097 job.waiting = False
1098 if job.waiting:
1099 continue
1100 if job.name in connection.functions:
1101 if not peek:
1102 queue.remove(job)
1103 connection.related_jobs[job.handle] = job
1104 job.worker_connection = connection
1105 job.running = True
1106 return job
1107 return None
1108
1109 def release(self, regex=None):
James E. Blaire7b99a02016-08-05 14:27:34 -07001110 """Release a held job.
1111
1112 :arg str regex: A regular expression which, if supplied, will
1113 cause only jobs with matching names to be released. If
1114 not supplied, all jobs will be released.
1115 """
Clark Boylanb640e052014-04-03 16:41:46 -07001116 released = False
1117 qlen = (len(self.high_queue) + len(self.normal_queue) +
1118 len(self.low_queue))
1119 self.log.debug("releasing queued job %s (%s)" % (regex, qlen))
1120 for job in self.getQueue():
Paul Belanger174a8272017-03-14 13:20:10 -04001121 if job.name != 'executor:execute':
Clark Boylanb640e052014-04-03 16:41:46 -07001122 continue
Paul Belanger6ab6af72016-11-06 11:32:59 -05001123 parameters = json.loads(job.arguments)
1124 if not regex or re.match(regex, parameters.get('job')):
Clark Boylanb640e052014-04-03 16:41:46 -07001125 self.log.debug("releasing queued job %s" %
1126 job.unique)
1127 job.waiting = False
1128 released = True
1129 else:
1130 self.log.debug("not releasing queued job %s" %
1131 job.unique)
1132 if released:
1133 self.wakeConnections()
1134 qlen = (len(self.high_queue) + len(self.normal_queue) +
1135 len(self.low_queue))
1136 self.log.debug("done releasing queued jobs %s (%s)" % (regex, qlen))
1137
1138
1139class FakeSMTP(object):
1140 log = logging.getLogger('zuul.FakeSMTP')
1141
1142 def __init__(self, messages, server, port):
1143 self.server = server
1144 self.port = port
1145 self.messages = messages
1146
1147 def sendmail(self, from_email, to_email, msg):
1148 self.log.info("Sending email from %s, to %s, with msg %s" % (
1149 from_email, to_email, msg))
1150
1151 headers = msg.split('\n\n', 1)[0]
1152 body = msg.split('\n\n', 1)[1]
1153
1154 self.messages.append(dict(
1155 from_email=from_email,
1156 to_email=to_email,
1157 msg=msg,
1158 headers=headers,
1159 body=body,
1160 ))
1161
1162 return True
1163
1164 def quit(self):
1165 return True
1166
1167
James E. Blairdce6cea2016-12-20 16:45:32 -08001168class FakeNodepool(object):
1169 REQUEST_ROOT = '/nodepool/requests'
James E. Blaire18d4602017-01-05 11:17:28 -08001170 NODE_ROOT = '/nodepool/nodes'
James E. Blairdce6cea2016-12-20 16:45:32 -08001171
1172 log = logging.getLogger("zuul.test.FakeNodepool")
1173
1174 def __init__(self, host, port, chroot):
1175 self.client = kazoo.client.KazooClient(
1176 hosts='%s:%s%s' % (host, port, chroot))
1177 self.client.start()
1178 self._running = True
James E. Blair15be0e12017-01-03 13:45:20 -08001179 self.paused = False
James E. Blairdce6cea2016-12-20 16:45:32 -08001180 self.thread = threading.Thread(target=self.run)
1181 self.thread.daemon = True
1182 self.thread.start()
James E. Blair6ab79e02017-01-06 10:10:17 -08001183 self.fail_requests = set()
James E. Blairdce6cea2016-12-20 16:45:32 -08001184
1185 def stop(self):
1186 self._running = False
1187 self.thread.join()
1188 self.client.stop()
1189 self.client.close()
1190
1191 def run(self):
1192 while self._running:
1193 self._run()
1194 time.sleep(0.1)
1195
1196 def _run(self):
James E. Blair15be0e12017-01-03 13:45:20 -08001197 if self.paused:
1198 return
James E. Blairdce6cea2016-12-20 16:45:32 -08001199 for req in self.getNodeRequests():
1200 self.fulfillRequest(req)
1201
1202 def getNodeRequests(self):
1203 try:
1204 reqids = self.client.get_children(self.REQUEST_ROOT)
1205 except kazoo.exceptions.NoNodeError:
1206 return []
1207 reqs = []
1208 for oid in sorted(reqids):
1209 path = self.REQUEST_ROOT + '/' + oid
James E. Blair0ef64f82017-02-02 11:25:16 -08001210 try:
1211 data, stat = self.client.get(path)
1212 data = json.loads(data)
1213 data['_oid'] = oid
1214 reqs.append(data)
1215 except kazoo.exceptions.NoNodeError:
1216 pass
James E. Blairdce6cea2016-12-20 16:45:32 -08001217 return reqs
1218
James E. Blaire18d4602017-01-05 11:17:28 -08001219 def getNodes(self):
1220 try:
1221 nodeids = self.client.get_children(self.NODE_ROOT)
1222 except kazoo.exceptions.NoNodeError:
1223 return []
1224 nodes = []
1225 for oid in sorted(nodeids):
1226 path = self.NODE_ROOT + '/' + oid
1227 data, stat = self.client.get(path)
1228 data = json.loads(data)
1229 data['_oid'] = oid
1230 try:
1231 lockfiles = self.client.get_children(path + '/lock')
1232 except kazoo.exceptions.NoNodeError:
1233 lockfiles = []
1234 if lockfiles:
1235 data['_lock'] = True
1236 else:
1237 data['_lock'] = False
1238 nodes.append(data)
1239 return nodes
1240
James E. Blaira38c28e2017-01-04 10:33:20 -08001241 def makeNode(self, request_id, node_type):
1242 now = time.time()
1243 path = '/nodepool/nodes/'
1244 data = dict(type=node_type,
1245 provider='test-provider',
1246 region='test-region',
Paul Belanger30ba93a2017-03-16 16:28:10 -04001247 az='test-az',
Monty Taylor56f61332017-04-11 05:38:12 -05001248 interface_ip='127.0.0.1',
James E. Blaira38c28e2017-01-04 10:33:20 -08001249 public_ipv4='127.0.0.1',
1250 private_ipv4=None,
1251 public_ipv6=None,
1252 allocated_to=request_id,
1253 state='ready',
1254 state_time=now,
1255 created_time=now,
1256 updated_time=now,
1257 image_id=None,
Paul Belangerc5bf3752017-03-16 19:38:43 -04001258 host_keys=["fake-key1", "fake-key2"],
Paul Belanger174a8272017-03-14 13:20:10 -04001259 executor='fake-nodepool')
James E. Blaira38c28e2017-01-04 10:33:20 -08001260 data = json.dumps(data)
1261 path = self.client.create(path, data,
1262 makepath=True,
1263 sequence=True)
1264 nodeid = path.split("/")[-1]
1265 return nodeid
1266
James E. Blair6ab79e02017-01-06 10:10:17 -08001267 def addFailRequest(self, request):
1268 self.fail_requests.add(request['_oid'])
1269
James E. Blairdce6cea2016-12-20 16:45:32 -08001270 def fulfillRequest(self, request):
James E. Blair6ab79e02017-01-06 10:10:17 -08001271 if request['state'] != 'requested':
James E. Blairdce6cea2016-12-20 16:45:32 -08001272 return
1273 request = request.copy()
James E. Blairdce6cea2016-12-20 16:45:32 -08001274 oid = request['_oid']
1275 del request['_oid']
James E. Blaira38c28e2017-01-04 10:33:20 -08001276
James E. Blair6ab79e02017-01-06 10:10:17 -08001277 if oid in self.fail_requests:
1278 request['state'] = 'failed'
1279 else:
1280 request['state'] = 'fulfilled'
1281 nodes = []
1282 for node in request['node_types']:
1283 nodeid = self.makeNode(oid, node)
1284 nodes.append(nodeid)
1285 request['nodes'] = nodes
James E. Blaira38c28e2017-01-04 10:33:20 -08001286
James E. Blaira38c28e2017-01-04 10:33:20 -08001287 request['state_time'] = time.time()
James E. Blairdce6cea2016-12-20 16:45:32 -08001288 path = self.REQUEST_ROOT + '/' + oid
1289 data = json.dumps(request)
1290 self.log.debug("Fulfilling node request: %s %s" % (oid, data))
1291 self.client.set(path, data)
1292
1293
James E. Blair498059b2016-12-20 13:50:13 -08001294class ChrootedKazooFixture(fixtures.Fixture):
Clark Boylan621ec9a2017-04-07 17:41:33 -07001295 def __init__(self, test_id):
James E. Blair498059b2016-12-20 13:50:13 -08001296 super(ChrootedKazooFixture, self).__init__()
1297
1298 zk_host = os.environ.get('NODEPOOL_ZK_HOST', 'localhost')
1299 if ':' in zk_host:
1300 host, port = zk_host.split(':')
1301 else:
1302 host = zk_host
1303 port = None
1304
1305 self.zookeeper_host = host
1306
1307 if not port:
1308 self.zookeeper_port = 2181
1309 else:
1310 self.zookeeper_port = int(port)
1311
Clark Boylan621ec9a2017-04-07 17:41:33 -07001312 self.test_id = test_id
1313
James E. Blair498059b2016-12-20 13:50:13 -08001314 def _setUp(self):
1315 # Make sure the test chroot paths do not conflict
1316 random_bits = ''.join(random.choice(string.ascii_lowercase +
1317 string.ascii_uppercase)
1318 for x in range(8))
1319
Clark Boylan621ec9a2017-04-07 17:41:33 -07001320 rand_test_path = '%s_%s_%s' % (random_bits, os.getpid(), self.test_id)
James E. Blair498059b2016-12-20 13:50:13 -08001321 self.zookeeper_chroot = "/nodepool_test/%s" % rand_test_path
1322
Clark Boylan7f5f1ec2017-04-07 17:39:56 -07001323 self.addCleanup(self._cleanup)
1324
James E. Blair498059b2016-12-20 13:50:13 -08001325 # Ensure the chroot path exists and clean up any pre-existing znodes.
1326 _tmp_client = kazoo.client.KazooClient(
1327 hosts='%s:%s' % (self.zookeeper_host, self.zookeeper_port))
1328 _tmp_client.start()
1329
1330 if _tmp_client.exists(self.zookeeper_chroot):
1331 _tmp_client.delete(self.zookeeper_chroot, recursive=True)
1332
1333 _tmp_client.ensure_path(self.zookeeper_chroot)
1334 _tmp_client.stop()
1335 _tmp_client.close()
1336
James E. Blair498059b2016-12-20 13:50:13 -08001337 def _cleanup(self):
1338 '''Remove the chroot path.'''
1339 # Need a non-chroot'ed client to remove the chroot path
1340 _tmp_client = kazoo.client.KazooClient(
1341 hosts='%s:%s' % (self.zookeeper_host, self.zookeeper_port))
1342 _tmp_client.start()
1343 _tmp_client.delete(self.zookeeper_chroot, recursive=True)
1344 _tmp_client.stop()
Clark Boylan7f5f1ec2017-04-07 17:39:56 -07001345 _tmp_client.close()
James E. Blair498059b2016-12-20 13:50:13 -08001346
1347
Joshua Heskethd78b4482015-09-14 16:56:34 -06001348class MySQLSchemaFixture(fixtures.Fixture):
1349 def setUp(self):
1350 super(MySQLSchemaFixture, self).setUp()
1351
1352 random_bits = ''.join(random.choice(string.ascii_lowercase +
1353 string.ascii_uppercase)
1354 for x in range(8))
1355 self.name = '%s_%s' % (random_bits, os.getpid())
1356 self.passwd = uuid.uuid4().hex
1357 db = pymysql.connect(host="localhost",
1358 user="openstack_citest",
1359 passwd="openstack_citest",
1360 db="openstack_citest")
1361 cur = db.cursor()
1362 cur.execute("create database %s" % self.name)
1363 cur.execute(
1364 "grant all on %s.* to '%s'@'localhost' identified by '%s'" %
1365 (self.name, self.name, self.passwd))
1366 cur.execute("flush privileges")
1367
1368 self.dburi = 'mysql+pymysql://%s:%s@localhost/%s' % (self.name,
1369 self.passwd,
1370 self.name)
1371 self.addDetail('dburi', testtools.content.text_content(self.dburi))
1372 self.addCleanup(self.cleanup)
1373
1374 def cleanup(self):
1375 db = pymysql.connect(host="localhost",
1376 user="openstack_citest",
1377 passwd="openstack_citest",
1378 db="openstack_citest")
1379 cur = db.cursor()
1380 cur.execute("drop database %s" % self.name)
1381 cur.execute("drop user '%s'@'localhost'" % self.name)
1382 cur.execute("flush privileges")
1383
1384
Maru Newby3fe5f852015-01-13 04:22:14 +00001385class BaseTestCase(testtools.TestCase):
Clark Boylanb640e052014-04-03 16:41:46 -07001386 log = logging.getLogger("zuul.test")
James E. Blair267e5162017-04-07 10:08:20 -07001387 wait_timeout = 30
Clark Boylanb640e052014-04-03 16:41:46 -07001388
James E. Blair1c236df2017-02-01 14:07:24 -08001389 def attachLogs(self, *args):
1390 def reader():
1391 self._log_stream.seek(0)
1392 while True:
1393 x = self._log_stream.read(4096)
1394 if not x:
1395 break
1396 yield x.encode('utf8')
1397 content = testtools.content.content_from_reader(
1398 reader,
1399 testtools.content_type.UTF8_TEXT,
1400 False)
1401 self.addDetail('logging', content)
1402
Clark Boylanb640e052014-04-03 16:41:46 -07001403 def setUp(self):
Maru Newby3fe5f852015-01-13 04:22:14 +00001404 super(BaseTestCase, self).setUp()
Clark Boylanb640e052014-04-03 16:41:46 -07001405 test_timeout = os.environ.get('OS_TEST_TIMEOUT', 0)
1406 try:
1407 test_timeout = int(test_timeout)
1408 except ValueError:
1409 # If timeout value is invalid do not set a timeout.
1410 test_timeout = 0
1411 if test_timeout > 0:
1412 self.useFixture(fixtures.Timeout(test_timeout, gentle=False))
1413
1414 if (os.environ.get('OS_STDOUT_CAPTURE') == 'True' or
1415 os.environ.get('OS_STDOUT_CAPTURE') == '1'):
1416 stdout = self.useFixture(fixtures.StringStream('stdout')).stream
1417 self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout))
1418 if (os.environ.get('OS_STDERR_CAPTURE') == 'True' or
1419 os.environ.get('OS_STDERR_CAPTURE') == '1'):
1420 stderr = self.useFixture(fixtures.StringStream('stderr')).stream
1421 self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr))
1422 if (os.environ.get('OS_LOG_CAPTURE') == 'True' or
1423 os.environ.get('OS_LOG_CAPTURE') == '1'):
James E. Blair1c236df2017-02-01 14:07:24 -08001424 self._log_stream = StringIO()
1425 self.addOnException(self.attachLogs)
1426 else:
1427 self._log_stream = sys.stdout
Maru Newby3fe5f852015-01-13 04:22:14 +00001428
James E. Blair1c236df2017-02-01 14:07:24 -08001429 handler = logging.StreamHandler(self._log_stream)
1430 formatter = logging.Formatter('%(asctime)s %(name)-32s '
1431 '%(levelname)-8s %(message)s')
1432 handler.setFormatter(formatter)
1433
1434 logger = logging.getLogger()
1435 logger.setLevel(logging.DEBUG)
1436 logger.addHandler(handler)
1437
Clark Boylan3410d532017-04-25 12:35:29 -07001438 # Make sure we don't carry old handlers around in process state
1439 # which slows down test runs
1440 self.addCleanup(logger.removeHandler, handler)
1441 self.addCleanup(handler.close)
1442 self.addCleanup(handler.flush)
1443
James E. Blair1c236df2017-02-01 14:07:24 -08001444 # NOTE(notmorgan): Extract logging overrides for specific
1445 # libraries from the OS_LOG_DEFAULTS env and create loggers
1446 # for each. This is used to limit the output during test runs
1447 # from libraries that zuul depends on such as gear.
James E. Blairdce6cea2016-12-20 16:45:32 -08001448 log_defaults_from_env = os.environ.get(
1449 'OS_LOG_DEFAULTS',
James E. Blair1c236df2017-02-01 14:07:24 -08001450 'git.cmd=INFO,kazoo.client=WARNING,gear=INFO')
Morgan Fainbergd34e0b42016-06-09 19:10:38 -07001451
James E. Blairdce6cea2016-12-20 16:45:32 -08001452 if log_defaults_from_env:
1453 for default in log_defaults_from_env.split(','):
1454 try:
1455 name, level_str = default.split('=', 1)
1456 level = getattr(logging, level_str, logging.DEBUG)
James E. Blair1c236df2017-02-01 14:07:24 -08001457 logger = logging.getLogger(name)
1458 logger.setLevel(level)
1459 logger.addHandler(handler)
1460 logger.propagate = False
James E. Blairdce6cea2016-12-20 16:45:32 -08001461 except ValueError:
1462 # NOTE(notmorgan): Invalid format of the log default,
1463 # skip and don't try and apply a logger for the
1464 # specified module
1465 pass
Morgan Fainbergd34e0b42016-06-09 19:10:38 -07001466
Maru Newby3fe5f852015-01-13 04:22:14 +00001467
1468class ZuulTestCase(BaseTestCase):
James E. Blaire7b99a02016-08-05 14:27:34 -07001469 """A test case with a functioning Zuul.
1470
1471 The following class variables are used during test setup and can
1472 be overidden by subclasses but are effectively read-only once a
1473 test method starts running:
1474
1475 :cvar str config_file: This points to the main zuul config file
1476 within the fixtures directory. Subclasses may override this
1477 to obtain a different behavior.
1478
1479 :cvar str tenant_config_file: This is the tenant config file
1480 (which specifies from what git repos the configuration should
1481 be loaded). It defaults to the value specified in
1482 `config_file` but can be overidden by subclasses to obtain a
1483 different tenant/project layout while using the standard main
James E. Blair06cc3922017-04-19 10:08:10 -07001484 configuration. See also the :py:func:`simple_layout`
1485 decorator.
James E. Blaire7b99a02016-08-05 14:27:34 -07001486
Ricardo Carrillo Cruz22994f92016-12-02 11:41:58 +00001487 :cvar bool create_project_keys: Indicates whether Zuul should
1488 auto-generate keys for each project, or whether the test
1489 infrastructure should insert dummy keys to save time during
1490 startup. Defaults to False.
1491
James E. Blaire7b99a02016-08-05 14:27:34 -07001492 The following are instance variables that are useful within test
1493 methods:
1494
1495 :ivar FakeGerritConnection fake_<connection>:
1496 A :py:class:`~tests.base.FakeGerritConnection` will be
1497 instantiated for each connection present in the config file
1498 and stored here. For instance, `fake_gerrit` will hold the
1499 FakeGerritConnection object for a connection named `gerrit`.
1500
1501 :ivar FakeGearmanServer gearman_server: An instance of
1502 :py:class:`~tests.base.FakeGearmanServer` which is the Gearman
1503 server that all of the Zuul components in this test use to
1504 communicate with each other.
1505
Paul Belanger174a8272017-03-14 13:20:10 -04001506 :ivar RecordingExecutorServer executor_server: An instance of
1507 :py:class:`~tests.base.RecordingExecutorServer` which is the
1508 Ansible execute server used to run jobs for this test.
James E. Blaire7b99a02016-08-05 14:27:34 -07001509
1510 :ivar list builds: A list of :py:class:`~tests.base.FakeBuild` objects
1511 representing currently running builds. They are appended to
Paul Belanger174a8272017-03-14 13:20:10 -04001512 the list in the order they are executed, and removed from this
James E. Blaire7b99a02016-08-05 14:27:34 -07001513 list upon completion.
1514
1515 :ivar list history: A list of :py:class:`~tests.base.BuildHistory`
1516 objects representing completed builds. They are appended to
1517 the list in the order they complete.
1518
1519 """
1520
James E. Blair83005782015-12-11 14:46:03 -08001521 config_file = 'zuul.conf'
James E. Blaire1767bc2016-08-02 10:00:27 -07001522 run_ansible = False
Ricardo Carrillo Cruz22994f92016-12-02 11:41:58 +00001523 create_project_keys = False
James E. Blair3f876d52016-07-22 13:07:14 -07001524
1525 def _startMerger(self):
1526 self.merge_server = zuul.merger.server.MergeServer(self.config,
1527 self.connections)
1528 self.merge_server.start()
1529
Maru Newby3fe5f852015-01-13 04:22:14 +00001530 def setUp(self):
1531 super(ZuulTestCase, self).setUp()
James E. Blair498059b2016-12-20 13:50:13 -08001532
1533 self.setupZK()
1534
K Jonathan Harkercc3a6f02017-02-22 19:08:06 -08001535 if not KEEP_TEMPDIRS:
James E. Blair97d902e2014-08-21 13:25:56 -07001536 tmp_root = self.useFixture(fixtures.TempDir(
Joshua Hesketh29d99b72014-08-19 16:27:42 +10001537 rootdir=os.environ.get("ZUUL_TEST_ROOT"))
1538 ).path
James E. Blair97d902e2014-08-21 13:25:56 -07001539 else:
K Jonathan Harkercc3a6f02017-02-22 19:08:06 -08001540 tmp_root = tempfile.mkdtemp(
1541 dir=os.environ.get("ZUUL_TEST_ROOT", None))
Clark Boylanb640e052014-04-03 16:41:46 -07001542 self.test_root = os.path.join(tmp_root, "zuul-test")
1543 self.upstream_root = os.path.join(self.test_root, "upstream")
Monty Taylord642d852017-02-23 14:05:42 -05001544 self.merger_src_root = os.path.join(self.test_root, "merger-git")
Paul Belanger174a8272017-03-14 13:20:10 -04001545 self.executor_src_root = os.path.join(self.test_root, "executor-git")
James E. Blairce8a2132016-05-19 15:21:52 -07001546 self.state_root = os.path.join(self.test_root, "lib")
Clark Boylanb640e052014-04-03 16:41:46 -07001547
1548 if os.path.exists(self.test_root):
1549 shutil.rmtree(self.test_root)
1550 os.makedirs(self.test_root)
1551 os.makedirs(self.upstream_root)
James E. Blairce8a2132016-05-19 15:21:52 -07001552 os.makedirs(self.state_root)
Clark Boylanb640e052014-04-03 16:41:46 -07001553
1554 # Make per test copy of Configuration.
1555 self.setup_config()
James E. Blair59fdbac2015-12-07 17:08:06 -08001556 self.config.set('zuul', 'tenant_config',
Joshua Heskethacccffc2015-03-31 23:38:17 +11001557 os.path.join(FIXTURE_DIR,
James E. Blair59fdbac2015-12-07 17:08:06 -08001558 self.config.get('zuul', 'tenant_config')))
Monty Taylord642d852017-02-23 14:05:42 -05001559 self.config.set('merger', 'git_dir', self.merger_src_root)
Paul Belanger174a8272017-03-14 13:20:10 -04001560 self.config.set('executor', 'git_dir', self.executor_src_root)
James E. Blairce8a2132016-05-19 15:21:52 -07001561 self.config.set('zuul', 'state_dir', self.state_root)
Clark Boylanb640e052014-04-03 16:41:46 -07001562
Clark Boylanb640e052014-04-03 16:41:46 -07001563 self.statsd = FakeStatsd()
Ian Wienandff977bf2015-09-30 15:38:47 +10001564 # note, use 127.0.0.1 rather than localhost to avoid getting ipv6
1565 # see: https://github.com/jsocol/pystatsd/issues/61
1566 os.environ['STATSD_HOST'] = '127.0.0.1'
Clark Boylanb640e052014-04-03 16:41:46 -07001567 os.environ['STATSD_PORT'] = str(self.statsd.port)
1568 self.statsd.start()
1569 # the statsd client object is configured in the statsd module import
Monty Taylor74fa3862016-06-02 07:39:49 +03001570 reload_module(statsd)
1571 reload_module(zuul.scheduler)
Clark Boylanb640e052014-04-03 16:41:46 -07001572
1573 self.gearman_server = FakeGearmanServer()
1574
1575 self.config.set('gearman', 'port', str(self.gearman_server.port))
James E. Blaire47eb772017-02-02 17:19:40 -08001576 self.log.info("Gearman server on port %s" %
1577 (self.gearman_server.port,))
Clark Boylanb640e052014-04-03 16:41:46 -07001578
James E. Blaire511d2f2016-12-08 15:22:26 -08001579 gerritsource.GerritSource.replication_timeout = 1.5
1580 gerritsource.GerritSource.replication_retry_interval = 0.5
1581 gerritconnection.GerritEventConnector.delay = 0.0
Clark Boylanb640e052014-04-03 16:41:46 -07001582
Joshua Hesketh352264b2015-08-11 23:42:08 +10001583 self.sched = zuul.scheduler.Scheduler(self.config)
Clark Boylanb640e052014-04-03 16:41:46 -07001584
Jan Hruban7083edd2015-08-21 14:00:54 +02001585 self.webapp = zuul.webapp.WebApp(
1586 self.sched, port=0, listen_address='127.0.0.1')
1587
Jan Hruban6b71aff2015-10-22 16:58:08 +02001588 self.event_queues = [
1589 self.sched.result_event_queue,
James E. Blair646322f2017-01-27 15:50:34 -08001590 self.sched.trigger_event_queue,
1591 self.sched.management_event_queue
Jan Hruban6b71aff2015-10-22 16:58:08 +02001592 ]
1593
James E. Blairfef78942016-03-11 16:28:56 -08001594 self.configure_connections()
Jan Hruban7083edd2015-08-21 14:00:54 +02001595 self.sched.registerConnections(self.connections, self.webapp)
Joshua Hesketh352264b2015-08-11 23:42:08 +10001596
Clark Boylanb640e052014-04-03 16:41:46 -07001597 def URLOpenerFactory(*args, **kw):
Morgan Fainberg293f7f82016-05-30 14:01:22 -07001598 if isinstance(args[0], urllib.request.Request):
Clark Boylanb640e052014-04-03 16:41:46 -07001599 return old_urlopen(*args, **kw)
Clark Boylanb640e052014-04-03 16:41:46 -07001600 return FakeURLOpener(self.upstream_root, *args, **kw)
1601
Morgan Fainberg293f7f82016-05-30 14:01:22 -07001602 old_urlopen = urllib.request.urlopen
1603 urllib.request.urlopen = URLOpenerFactory
Clark Boylanb640e052014-04-03 16:41:46 -07001604
James E. Blair3f876d52016-07-22 13:07:14 -07001605 self._startMerger()
James E. Blair3f876d52016-07-22 13:07:14 -07001606
Paul Belanger174a8272017-03-14 13:20:10 -04001607 self.executor_server = RecordingExecutorServer(
James E. Blaira92cbc82017-01-23 14:56:49 -08001608 self.config, self.connections,
James E. Blair854f8892017-02-02 11:25:39 -08001609 jobdir_root=self.test_root,
James E. Blaira92cbc82017-01-23 14:56:49 -08001610 _run_ansible=self.run_ansible,
K Jonathan Harkercc3a6f02017-02-22 19:08:06 -08001611 _test_root=self.test_root,
1612 keep_jobdir=KEEP_TEMPDIRS)
Paul Belanger174a8272017-03-14 13:20:10 -04001613 self.executor_server.start()
1614 self.history = self.executor_server.build_history
1615 self.builds = self.executor_server.running_builds
James E. Blaire1767bc2016-08-02 10:00:27 -07001616
Paul Belanger174a8272017-03-14 13:20:10 -04001617 self.executor_client = zuul.executor.client.ExecutorClient(
James E. Blair92e953a2017-03-07 13:08:47 -08001618 self.config, self.sched)
Joshua Hesketh850ccb62014-11-27 11:31:02 +11001619 self.merge_client = zuul.merger.client.MergeClient(
1620 self.config, self.sched)
James E. Blair8d692392016-04-08 17:47:58 -07001621 self.nodepool = zuul.nodepool.Nodepool(self.sched)
James E. Blairdce6cea2016-12-20 16:45:32 -08001622 self.zk = zuul.zk.ZooKeeper()
James E. Blair0d5a36e2017-02-21 10:53:44 -05001623 self.zk.connect(self.zk_config)
James E. Blairdce6cea2016-12-20 16:45:32 -08001624
James E. Blair0d5a36e2017-02-21 10:53:44 -05001625 self.fake_nodepool = FakeNodepool(
1626 self.zk_chroot_fixture.zookeeper_host,
1627 self.zk_chroot_fixture.zookeeper_port,
1628 self.zk_chroot_fixture.zookeeper_chroot)
Clark Boylanb640e052014-04-03 16:41:46 -07001629
Paul Belanger174a8272017-03-14 13:20:10 -04001630 self.sched.setExecutor(self.executor_client)
Clark Boylanb640e052014-04-03 16:41:46 -07001631 self.sched.setMerger(self.merge_client)
James E. Blair8d692392016-04-08 17:47:58 -07001632 self.sched.setNodepool(self.nodepool)
James E. Blairdce6cea2016-12-20 16:45:32 -08001633 self.sched.setZooKeeper(self.zk)
Clark Boylanb640e052014-04-03 16:41:46 -07001634
Joshua Hesketh850ccb62014-11-27 11:31:02 +11001635 self.rpc = zuul.rpclistener.RPCListener(self.config, self.sched)
Clark Boylanb640e052014-04-03 16:41:46 -07001636
1637 self.sched.start()
Clark Boylanb640e052014-04-03 16:41:46 -07001638 self.webapp.start()
1639 self.rpc.start()
Paul Belanger174a8272017-03-14 13:20:10 -04001640 self.executor_client.gearman.waitForServer()
James E. Blaira002b032017-04-18 10:35:48 -07001641 # Cleanups are run in reverse order
1642 self.addCleanup(self.assertCleanShutdown)
Clark Boylanb640e052014-04-03 16:41:46 -07001643 self.addCleanup(self.shutdown)
James E. Blaira002b032017-04-18 10:35:48 -07001644 self.addCleanup(self.assertFinalState)
Clark Boylanb640e052014-04-03 16:41:46 -07001645
James E. Blairb9c0d772017-03-03 14:34:49 -08001646 self.sched.reconfigure(self.config)
1647 self.sched.resume()
1648
James E. Blairfef78942016-03-11 16:28:56 -08001649 def configure_connections(self):
James E. Blaire511d2f2016-12-08 15:22:26 -08001650 # Set up gerrit related fakes
1651 # Set a changes database so multiple FakeGerrit's can report back to
1652 # a virtual canonical database given by the configured hostname
1653 self.gerrit_changes_dbs = {}
1654
1655 def getGerritConnection(driver, name, config):
1656 db = self.gerrit_changes_dbs.setdefault(config['server'], {})
1657 con = FakeGerritConnection(driver, name, config,
1658 changes_db=db,
1659 upstream_root=self.upstream_root)
1660 self.event_queues.append(con.event_queue)
1661 setattr(self, 'fake_' + name, con)
1662 return con
1663
1664 self.useFixture(fixtures.MonkeyPatch(
1665 'zuul.driver.gerrit.GerritDriver.getConnection',
1666 getGerritConnection))
1667
Gregory Haynes4fc12542015-04-22 20:38:06 -07001668 def getGithubConnection(driver, name, config):
1669 con = FakeGithubConnection(driver, name, config,
1670 upstream_root=self.upstream_root)
1671 setattr(self, 'fake_' + name, con)
1672 return con
1673
1674 self.useFixture(fixtures.MonkeyPatch(
1675 'zuul.driver.github.GithubDriver.getConnection',
1676 getGithubConnection))
1677
James E. Blaire511d2f2016-12-08 15:22:26 -08001678 # Set up smtp related fakes
Joshua Heskethd78b4482015-09-14 16:56:34 -06001679 # TODO(jhesketh): This should come from lib.connections for better
1680 # coverage
Joshua Hesketh352264b2015-08-11 23:42:08 +10001681 # Register connections from the config
1682 self.smtp_messages = []
Joshua Hesketh850ccb62014-11-27 11:31:02 +11001683
Joshua Hesketh352264b2015-08-11 23:42:08 +10001684 def FakeSMTPFactory(*args, **kw):
1685 args = [self.smtp_messages] + list(args)
1686 return FakeSMTP(*args, **kw)
Joshua Hesketh850ccb62014-11-27 11:31:02 +11001687
Joshua Hesketh352264b2015-08-11 23:42:08 +10001688 self.useFixture(fixtures.MonkeyPatch('smtplib.SMTP', FakeSMTPFactory))
Joshua Hesketh850ccb62014-11-27 11:31:02 +11001689
James E. Blaire511d2f2016-12-08 15:22:26 -08001690 # Register connections from the config using fakes
James E. Blairfef78942016-03-11 16:28:56 -08001691 self.connections = zuul.lib.connections.ConnectionRegistry()
James E. Blaire511d2f2016-12-08 15:22:26 -08001692 self.connections.configure(self.config)
Joshua Hesketh850ccb62014-11-27 11:31:02 +11001693
James E. Blair83005782015-12-11 14:46:03 -08001694 def setup_config(self):
James E. Blaire7b99a02016-08-05 14:27:34 -07001695 # This creates the per-test configuration object. It can be
1696 # overriden by subclasses, but should not need to be since it
1697 # obeys the config_file and tenant_config_file attributes.
Clark Boylanb640e052014-04-03 16:41:46 -07001698 self.config = ConfigParser.ConfigParser()
James E. Blair83005782015-12-11 14:46:03 -08001699 self.config.read(os.path.join(FIXTURE_DIR, self.config_file))
James E. Blair06cc3922017-04-19 10:08:10 -07001700
1701 if not self.setupSimpleLayout():
1702 if hasattr(self, 'tenant_config_file'):
1703 self.config.set('zuul', 'tenant_config',
1704 self.tenant_config_file)
1705 git_path = os.path.join(
1706 os.path.dirname(
1707 os.path.join(FIXTURE_DIR, self.tenant_config_file)),
1708 'git')
1709 if os.path.exists(git_path):
1710 for reponame in os.listdir(git_path):
1711 project = reponame.replace('_', '/')
1712 self.copyDirToRepo(project,
1713 os.path.join(git_path, reponame))
Ricardo Carrillo Cruz22994f92016-12-02 11:41:58 +00001714 self.setupAllProjectKeys()
1715
James E. Blair06cc3922017-04-19 10:08:10 -07001716 def setupSimpleLayout(self):
1717 # If the test method has been decorated with a simple_layout,
1718 # use that instead of the class tenant_config_file. Set up a
1719 # single config-project with the specified layout, and
1720 # initialize repos for all of the 'project' entries which
1721 # appear in the layout.
1722 test_name = self.id().split('.')[-1]
1723 test = getattr(self, test_name)
1724 if hasattr(test, '__simple_layout__'):
Jesse Keating436a5452017-04-20 11:48:41 -07001725 path, driver = getattr(test, '__simple_layout__')
James E. Blair06cc3922017-04-19 10:08:10 -07001726 else:
1727 return False
1728
James E. Blairb70e55a2017-04-19 12:57:02 -07001729 files = {}
James E. Blair06cc3922017-04-19 10:08:10 -07001730 path = os.path.join(FIXTURE_DIR, path)
1731 with open(path) as f:
James E. Blairb70e55a2017-04-19 12:57:02 -07001732 data = f.read()
1733 layout = yaml.safe_load(data)
1734 files['zuul.yaml'] = data
James E. Blair06cc3922017-04-19 10:08:10 -07001735 untrusted_projects = []
1736 for item in layout:
1737 if 'project' in item:
1738 name = item['project']['name']
1739 untrusted_projects.append(name)
1740 self.init_repo(name)
1741 self.addCommitToRepo(name, 'initial commit',
1742 files={'README': ''},
1743 branch='master', tag='init')
James E. Blairb70e55a2017-04-19 12:57:02 -07001744 if 'job' in item:
1745 jobname = item['job']['name']
1746 files['playbooks/%s.yaml' % jobname] = ''
James E. Blair06cc3922017-04-19 10:08:10 -07001747
1748 root = os.path.join(self.test_root, "config")
1749 if not os.path.exists(root):
1750 os.makedirs(root)
1751 f = tempfile.NamedTemporaryFile(dir=root, delete=False)
1752 config = [{'tenant':
1753 {'name': 'tenant-one',
Jesse Keating436a5452017-04-20 11:48:41 -07001754 'source': {driver:
James E. Blair06cc3922017-04-19 10:08:10 -07001755 {'config-projects': ['common-config'],
1756 'untrusted-projects': untrusted_projects}}}}]
1757 f.write(yaml.dump(config))
1758 f.close()
1759 self.config.set('zuul', 'tenant_config',
1760 os.path.join(FIXTURE_DIR, f.name))
1761
1762 self.init_repo('common-config')
James E. Blair06cc3922017-04-19 10:08:10 -07001763 self.addCommitToRepo('common-config', 'add content from fixture',
1764 files, branch='master', tag='init')
1765
1766 return True
1767
Ricardo Carrillo Cruz22994f92016-12-02 11:41:58 +00001768 def setupAllProjectKeys(self):
1769 if self.create_project_keys:
1770 return
1771
1772 path = self.config.get('zuul', 'tenant_config')
1773 with open(os.path.join(FIXTURE_DIR, path)) as f:
1774 tenant_config = yaml.safe_load(f.read())
1775 for tenant in tenant_config:
1776 sources = tenant['tenant']['source']
1777 for source, conf in sources.items():
James E. Blair109da3f2017-04-04 14:39:43 -07001778 for project in conf.get('config-projects', []):
Ricardo Carrillo Cruz22994f92016-12-02 11:41:58 +00001779 self.setupProjectKeys(source, project)
James E. Blair109da3f2017-04-04 14:39:43 -07001780 for project in conf.get('untrusted-projects', []):
Ricardo Carrillo Cruz22994f92016-12-02 11:41:58 +00001781 self.setupProjectKeys(source, project)
1782
1783 def setupProjectKeys(self, source, project):
1784 # Make sure we set up an RSA key for the project so that we
1785 # don't spend time generating one:
1786
1787 key_root = os.path.join(self.state_root, 'keys')
1788 if not os.path.isdir(key_root):
1789 os.mkdir(key_root, 0o700)
1790 private_key_file = os.path.join(key_root, source, project + '.pem')
1791 private_key_dir = os.path.dirname(private_key_file)
1792 self.log.debug("Installing test keys for project %s at %s" % (
1793 project, private_key_file))
1794 if not os.path.isdir(private_key_dir):
1795 os.makedirs(private_key_dir)
1796 with open(os.path.join(FIXTURE_DIR, 'private.pem')) as i:
1797 with open(private_key_file, 'w') as o:
1798 o.write(i.read())
James E. Blair96c6bf82016-01-15 16:20:40 -08001799
James E. Blair498059b2016-12-20 13:50:13 -08001800 def setupZK(self):
Clark Boylan621ec9a2017-04-07 17:41:33 -07001801 self.zk_chroot_fixture = self.useFixture(
1802 ChrootedKazooFixture(self.id()))
James E. Blair0d5a36e2017-02-21 10:53:44 -05001803 self.zk_config = '%s:%s%s' % (
James E. Blairdce6cea2016-12-20 16:45:32 -08001804 self.zk_chroot_fixture.zookeeper_host,
1805 self.zk_chroot_fixture.zookeeper_port,
1806 self.zk_chroot_fixture.zookeeper_chroot)
James E. Blair498059b2016-12-20 13:50:13 -08001807
James E. Blair96c6bf82016-01-15 16:20:40 -08001808 def copyDirToRepo(self, project, source_path):
James E. Blair8b1dc3f2016-07-05 16:49:00 -07001809 self.init_repo(project)
James E. Blair96c6bf82016-01-15 16:20:40 -08001810
1811 files = {}
1812 for (dirpath, dirnames, filenames) in os.walk(source_path):
1813 for filename in filenames:
1814 test_tree_filepath = os.path.join(dirpath, filename)
1815 common_path = os.path.commonprefix([test_tree_filepath,
1816 source_path])
1817 relative_filepath = test_tree_filepath[len(common_path) + 1:]
1818 with open(test_tree_filepath, 'r') as f:
1819 content = f.read()
1820 files[relative_filepath] = content
1821 self.addCommitToRepo(project, 'add content from fixture',
James E. Blair8b1dc3f2016-07-05 16:49:00 -07001822 files, branch='master', tag='init')
James E. Blair83005782015-12-11 14:46:03 -08001823
James E. Blaire18d4602017-01-05 11:17:28 -08001824 def assertNodepoolState(self):
1825 # Make sure that there are no pending requests
1826
1827 requests = self.fake_nodepool.getNodeRequests()
1828 self.assertEqual(len(requests), 0)
1829
1830 nodes = self.fake_nodepool.getNodes()
1831 for node in nodes:
1832 self.assertFalse(node['_lock'], "Node %s is locked" %
1833 (node['_oid'],))
1834
Ricardo Carrillo Cruz22994f92016-12-02 11:41:58 +00001835 def assertNoGeneratedKeys(self):
1836 # Make sure that Zuul did not generate any project keys
1837 # (unless it was supposed to).
1838
1839 if self.create_project_keys:
1840 return
1841
1842 with open(os.path.join(FIXTURE_DIR, 'private.pem')) as i:
1843 test_key = i.read()
1844
1845 key_root = os.path.join(self.state_root, 'keys')
1846 for root, dirname, files in os.walk(key_root):
1847 for fn in files:
1848 with open(os.path.join(root, fn)) as f:
1849 self.assertEqual(test_key, f.read())
1850
Clark Boylanb640e052014-04-03 16:41:46 -07001851 def assertFinalState(self):
James E. Blaira002b032017-04-18 10:35:48 -07001852 self.log.debug("Assert final state")
1853 # Make sure no jobs are running
1854 self.assertEqual({}, self.executor_server.job_workers)
Clark Boylanb640e052014-04-03 16:41:46 -07001855 # Make sure that git.Repo objects have been garbage collected.
1856 repos = []
1857 gc.collect()
1858 for obj in gc.get_objects():
1859 if isinstance(obj, git.Repo):
James E. Blairc43525f2017-03-03 11:10:26 -08001860 self.log.debug("Leaked git repo object: %s" % repr(obj))
Clark Boylanb640e052014-04-03 16:41:46 -07001861 repos.append(obj)
1862 self.assertEqual(len(repos), 0)
1863 self.assertEmptyQueues()
James E. Blaire18d4602017-01-05 11:17:28 -08001864 self.assertNodepoolState()
Ricardo Carrillo Cruz22994f92016-12-02 11:41:58 +00001865 self.assertNoGeneratedKeys()
James E. Blair83005782015-12-11 14:46:03 -08001866 ipm = zuul.manager.independent.IndependentPipelineManager
James E. Blair59fdbac2015-12-07 17:08:06 -08001867 for tenant in self.sched.abide.tenants.values():
1868 for pipeline in tenant.layout.pipelines.values():
James E. Blair83005782015-12-11 14:46:03 -08001869 if isinstance(pipeline.manager, ipm):
James E. Blair59fdbac2015-12-07 17:08:06 -08001870 self.assertEqual(len(pipeline.queues), 0)
Clark Boylanb640e052014-04-03 16:41:46 -07001871
1872 def shutdown(self):
1873 self.log.debug("Shutting down after tests")
Paul Belanger174a8272017-03-14 13:20:10 -04001874 self.executor_client.stop()
James E. Blair3f876d52016-07-22 13:07:14 -07001875 self.merge_server.stop()
1876 self.merge_server.join()
Clark Boylanb640e052014-04-03 16:41:46 -07001877 self.merge_client.stop()
Paul Belanger174a8272017-03-14 13:20:10 -04001878 self.executor_server.stop()
Clark Boylanb640e052014-04-03 16:41:46 -07001879 self.sched.stop()
1880 self.sched.join()
1881 self.statsd.stop()
1882 self.statsd.join()
1883 self.webapp.stop()
1884 self.webapp.join()
1885 self.rpc.stop()
1886 self.rpc.join()
1887 self.gearman_server.shutdown()
James E. Blairdce6cea2016-12-20 16:45:32 -08001888 self.fake_nodepool.stop()
1889 self.zk.disconnect()
Clark Boylan8208c192017-04-24 18:08:08 -07001890 self.printHistory()
Clark Boylanf18e3b82017-04-24 17:34:13 -07001891 # we whitelist watchdog threads as they have relatively long delays
1892 # before noticing they should exit, but they should exit on their own.
1893 threads = [t for t in threading.enumerate()
1894 if t.name != 'executor-watchdog']
Clark Boylanb640e052014-04-03 16:41:46 -07001895 if len(threads) > 1:
Clark Boylan8208c192017-04-24 18:08:08 -07001896 log_str = ""
1897 for thread_id, stack_frame in sys._current_frames().items():
1898 log_str += "Thread: %s\n" % thread_id
1899 log_str += "".join(traceback.format_stack(stack_frame))
1900 self.log.debug(log_str)
1901 raise Exception("More than one thread is running: %s" % threads)
Clark Boylanb640e052014-04-03 16:41:46 -07001902
James E. Blaira002b032017-04-18 10:35:48 -07001903 def assertCleanShutdown(self):
1904 pass
1905
James E. Blairc4ba97a2017-04-19 16:26:24 -07001906 def init_repo(self, project, tag=None):
Clark Boylanb640e052014-04-03 16:41:46 -07001907 parts = project.split('/')
1908 path = os.path.join(self.upstream_root, *parts[:-1])
1909 if not os.path.exists(path):
1910 os.makedirs(path)
1911 path = os.path.join(self.upstream_root, project)
1912 repo = git.Repo.init(path)
1913
Morgan Fainberg78c301a2016-07-14 13:47:01 -07001914 with repo.config_writer() as config_writer:
1915 config_writer.set_value('user', 'email', 'user@example.com')
1916 config_writer.set_value('user', 'name', 'User Name')
Clark Boylanb640e052014-04-03 16:41:46 -07001917
Clark Boylanb640e052014-04-03 16:41:46 -07001918 repo.index.commit('initial commit')
1919 master = repo.create_head('master')
James E. Blairc4ba97a2017-04-19 16:26:24 -07001920 if tag:
1921 repo.create_tag(tag)
Clark Boylanb640e052014-04-03 16:41:46 -07001922
James E. Blair97d902e2014-08-21 13:25:56 -07001923 repo.head.reference = master
James E. Blair879dafb2015-07-17 14:04:49 -07001924 zuul.merger.merger.reset_repo_to_head(repo)
James E. Blair97d902e2014-08-21 13:25:56 -07001925 repo.git.clean('-x', '-f', '-d')
1926
James E. Blair97d902e2014-08-21 13:25:56 -07001927 def create_branch(self, project, branch):
1928 path = os.path.join(self.upstream_root, project)
1929 repo = git.Repo.init(path)
1930 fn = os.path.join(path, 'README')
1931
1932 branch_head = repo.create_head(branch)
1933 repo.head.reference = branch_head
Clark Boylanb640e052014-04-03 16:41:46 -07001934 f = open(fn, 'a')
James E. Blair97d902e2014-08-21 13:25:56 -07001935 f.write("test %s\n" % branch)
Clark Boylanb640e052014-04-03 16:41:46 -07001936 f.close()
1937 repo.index.add([fn])
James E. Blair97d902e2014-08-21 13:25:56 -07001938 repo.index.commit('%s commit' % branch)
Clark Boylanb640e052014-04-03 16:41:46 -07001939
James E. Blair97d902e2014-08-21 13:25:56 -07001940 repo.head.reference = repo.heads['master']
James E. Blair879dafb2015-07-17 14:04:49 -07001941 zuul.merger.merger.reset_repo_to_head(repo)
Clark Boylanb640e052014-04-03 16:41:46 -07001942 repo.git.clean('-x', '-f', '-d')
1943
Sachi King9f16d522016-03-16 12:20:45 +11001944 def create_commit(self, project):
1945 path = os.path.join(self.upstream_root, project)
1946 repo = git.Repo(path)
1947 repo.head.reference = repo.heads['master']
1948 file_name = os.path.join(path, 'README')
1949 with open(file_name, 'a') as f:
1950 f.write('creating fake commit\n')
1951 repo.index.add([file_name])
1952 commit = repo.index.commit('Creating a fake commit')
1953 return commit.hexsha
1954
James E. Blairf4a5f022017-04-18 14:01:10 -07001955 def orderedRelease(self, count=None):
James E. Blairb8c16472015-05-05 14:55:26 -07001956 # Run one build at a time to ensure non-race order:
James E. Blairf4a5f022017-04-18 14:01:10 -07001957 i = 0
James E. Blairb8c16472015-05-05 14:55:26 -07001958 while len(self.builds):
1959 self.release(self.builds[0])
1960 self.waitUntilSettled()
James E. Blairf4a5f022017-04-18 14:01:10 -07001961 i += 1
1962 if count is not None and i >= count:
1963 break
James E. Blairb8c16472015-05-05 14:55:26 -07001964
Clark Boylanb640e052014-04-03 16:41:46 -07001965 def release(self, job):
1966 if isinstance(job, FakeBuild):
1967 job.release()
1968 else:
1969 job.waiting = False
1970 self.log.debug("Queued job %s released" % job.unique)
1971 self.gearman_server.wakeConnections()
1972
1973 def getParameter(self, job, name):
1974 if isinstance(job, FakeBuild):
1975 return job.parameters[name]
1976 else:
1977 parameters = json.loads(job.arguments)
1978 return parameters[name]
1979
Clark Boylanb640e052014-04-03 16:41:46 -07001980 def haveAllBuildsReported(self):
1981 # See if Zuul is waiting on a meta job to complete
Paul Belanger174a8272017-03-14 13:20:10 -04001982 if self.executor_client.meta_jobs:
Clark Boylanb640e052014-04-03 16:41:46 -07001983 return False
1984 # Find out if every build that the worker has completed has been
1985 # reported back to Zuul. If it hasn't then that means a Gearman
1986 # event is still in transit and the system is not stable.
James E. Blair3f876d52016-07-22 13:07:14 -07001987 for build in self.history:
Paul Belanger174a8272017-03-14 13:20:10 -04001988 zbuild = self.executor_client.builds.get(build.uuid)
Clark Boylanb640e052014-04-03 16:41:46 -07001989 if not zbuild:
1990 # It has already been reported
1991 continue
1992 # It hasn't been reported yet.
1993 return False
1994 # Make sure that none of the worker connections are in GRAB_WAIT
Paul Belanger174a8272017-03-14 13:20:10 -04001995 for connection in self.executor_server.worker.active_connections:
Clark Boylanb640e052014-04-03 16:41:46 -07001996 if connection.state == 'GRAB_WAIT':
1997 return False
1998 return True
1999
2000 def areAllBuildsWaiting(self):
Paul Belanger174a8272017-03-14 13:20:10 -04002001 builds = self.executor_client.builds.values()
James E. Blaira002b032017-04-18 10:35:48 -07002002 seen_builds = set()
Clark Boylanb640e052014-04-03 16:41:46 -07002003 for build in builds:
James E. Blaira002b032017-04-18 10:35:48 -07002004 seen_builds.add(build.uuid)
Clark Boylanb640e052014-04-03 16:41:46 -07002005 client_job = None
Paul Belanger174a8272017-03-14 13:20:10 -04002006 for conn in self.executor_client.gearman.active_connections:
Clark Boylanb640e052014-04-03 16:41:46 -07002007 for j in conn.related_jobs.values():
2008 if j.unique == build.uuid:
2009 client_job = j
2010 break
2011 if not client_job:
2012 self.log.debug("%s is not known to the gearman client" %
2013 build)
James E. Blairf15139b2015-04-02 16:37:15 -07002014 return False
Clark Boylanb640e052014-04-03 16:41:46 -07002015 if not client_job.handle:
2016 self.log.debug("%s has no handle" % client_job)
James E. Blairf15139b2015-04-02 16:37:15 -07002017 return False
Clark Boylanb640e052014-04-03 16:41:46 -07002018 server_job = self.gearman_server.jobs.get(client_job.handle)
2019 if not server_job:
2020 self.log.debug("%s is not known to the gearman server" %
2021 client_job)
James E. Blairf15139b2015-04-02 16:37:15 -07002022 return False
Clark Boylanb640e052014-04-03 16:41:46 -07002023 if not hasattr(server_job, 'waiting'):
2024 self.log.debug("%s is being enqueued" % server_job)
James E. Blairf15139b2015-04-02 16:37:15 -07002025 return False
Clark Boylanb640e052014-04-03 16:41:46 -07002026 if server_job.waiting:
2027 continue
James E. Blair17302972016-08-10 16:11:42 -07002028 if build.url is None:
James E. Blairbbda4702016-03-09 15:19:56 -08002029 self.log.debug("%s has not reported start" % build)
2030 return False
Paul Belanger174a8272017-03-14 13:20:10 -04002031 worker_build = self.executor_server.job_builds.get(
2032 server_job.unique)
James E. Blair962220f2016-08-03 11:22:38 -07002033 if worker_build:
2034 if worker_build.isWaiting():
2035 continue
2036 else:
2037 self.log.debug("%s is running" % worker_build)
2038 return False
Clark Boylanb640e052014-04-03 16:41:46 -07002039 else:
James E. Blair962220f2016-08-03 11:22:38 -07002040 self.log.debug("%s is unassigned" % server_job)
James E. Blairf15139b2015-04-02 16:37:15 -07002041 return False
James E. Blaira002b032017-04-18 10:35:48 -07002042 for (build_uuid, job_worker) in \
2043 self.executor_server.job_workers.items():
2044 if build_uuid not in seen_builds:
2045 self.log.debug("%s is not finalized" % build_uuid)
2046 return False
James E. Blairf15139b2015-04-02 16:37:15 -07002047 return True
Clark Boylanb640e052014-04-03 16:41:46 -07002048
James E. Blairdce6cea2016-12-20 16:45:32 -08002049 def areAllNodeRequestsComplete(self):
James E. Blair15be0e12017-01-03 13:45:20 -08002050 if self.fake_nodepool.paused:
2051 return True
James E. Blairdce6cea2016-12-20 16:45:32 -08002052 if self.sched.nodepool.requests:
2053 return False
2054 return True
2055
Jan Hruban6b71aff2015-10-22 16:58:08 +02002056 def eventQueuesEmpty(self):
2057 for queue in self.event_queues:
2058 yield queue.empty()
2059
2060 def eventQueuesJoin(self):
2061 for queue in self.event_queues:
2062 queue.join()
2063
Clark Boylanb640e052014-04-03 16:41:46 -07002064 def waitUntilSettled(self):
2065 self.log.debug("Waiting until settled...")
2066 start = time.time()
2067 while True:
Clint Byruma9626572017-02-22 14:04:00 -05002068 if time.time() - start > self.wait_timeout:
James E. Blair10fc1eb2016-12-21 16:16:25 -08002069 self.log.error("Timeout waiting for Zuul to settle")
2070 self.log.error("Queue status:")
James E. Blair622c9682016-06-09 08:14:53 -07002071 for queue in self.event_queues:
James E. Blair10fc1eb2016-12-21 16:16:25 -08002072 self.log.error(" %s: %s" % (queue, queue.empty()))
2073 self.log.error("All builds waiting: %s" %
James E. Blair622c9682016-06-09 08:14:53 -07002074 (self.areAllBuildsWaiting(),))
James E. Blair10fc1eb2016-12-21 16:16:25 -08002075 self.log.error("All builds reported: %s" %
James E. Blairf3156c92016-08-10 15:32:19 -07002076 (self.haveAllBuildsReported(),))
James E. Blair10fc1eb2016-12-21 16:16:25 -08002077 self.log.error("All requests completed: %s" %
2078 (self.areAllNodeRequestsComplete(),))
2079 self.log.error("Merge client jobs: %s" %
2080 (self.merge_client.jobs,))
Clark Boylanb640e052014-04-03 16:41:46 -07002081 raise Exception("Timeout waiting for Zuul to settle")
2082 # Make sure no new events show up while we're checking
James E. Blair3f876d52016-07-22 13:07:14 -07002083
Paul Belanger174a8272017-03-14 13:20:10 -04002084 self.executor_server.lock.acquire()
Clark Boylanb640e052014-04-03 16:41:46 -07002085 # have all build states propogated to zuul?
2086 if self.haveAllBuildsReported():
2087 # Join ensures that the queue is empty _and_ events have been
2088 # processed
Jan Hruban6b71aff2015-10-22 16:58:08 +02002089 self.eventQueuesJoin()
Clark Boylanb640e052014-04-03 16:41:46 -07002090 self.sched.run_handler_lock.acquire()
James E. Blair14abdf42015-12-09 16:11:53 -08002091 if (not self.merge_client.jobs and
Clark Boylanb640e052014-04-03 16:41:46 -07002092 self.haveAllBuildsReported() and
James E. Blairdce6cea2016-12-20 16:45:32 -08002093 self.areAllBuildsWaiting() and
James E. Blair36c611a2017-02-06 15:59:43 -08002094 self.areAllNodeRequestsComplete() and
2095 all(self.eventQueuesEmpty())):
2096 # The queue empty check is placed at the end to
2097 # ensure that if a component adds an event between
2098 # when locked the run handler and checked that the
2099 # components were stable, we don't erroneously
2100 # report that we are settled.
Clark Boylanb640e052014-04-03 16:41:46 -07002101 self.sched.run_handler_lock.release()
Paul Belanger174a8272017-03-14 13:20:10 -04002102 self.executor_server.lock.release()
Clark Boylanb640e052014-04-03 16:41:46 -07002103 self.log.debug("...settled.")
2104 return
2105 self.sched.run_handler_lock.release()
Paul Belanger174a8272017-03-14 13:20:10 -04002106 self.executor_server.lock.release()
Clark Boylanb640e052014-04-03 16:41:46 -07002107 self.sched.wake_event.wait(0.1)
2108
2109 def countJobResults(self, jobs, result):
2110 jobs = filter(lambda x: x.result == result, jobs)
2111 return len(jobs)
2112
James E. Blair96c6bf82016-01-15 16:20:40 -08002113 def getJobFromHistory(self, name, project=None):
James E. Blair3f876d52016-07-22 13:07:14 -07002114 for job in self.history:
2115 if (job.name == name and
2116 (project is None or
2117 job.parameters['ZUUL_PROJECT'] == project)):
2118 return job
Clark Boylanb640e052014-04-03 16:41:46 -07002119 raise Exception("Unable to find job %s in history" % name)
2120
2121 def assertEmptyQueues(self):
2122 # Make sure there are no orphaned jobs
James E. Blair59fdbac2015-12-07 17:08:06 -08002123 for tenant in self.sched.abide.tenants.values():
2124 for pipeline in tenant.layout.pipelines.values():
2125 for queue in pipeline.queues:
2126 if len(queue.queue) != 0:
Joshua Hesketh0aa7e8b2016-07-14 00:12:25 +10002127 print('pipeline %s queue %s contents %s' % (
2128 pipeline.name, queue.name, queue.queue))
James E. Blair59fdbac2015-12-07 17:08:06 -08002129 self.assertEqual(len(queue.queue), 0,
2130 "Pipelines queues should be empty")
Clark Boylanb640e052014-04-03 16:41:46 -07002131
2132 def assertReportedStat(self, key, value=None, kind=None):
2133 start = time.time()
2134 while time.time() < (start + 5):
2135 for stat in self.statsd.stats:
Clark Boylanb640e052014-04-03 16:41:46 -07002136 k, v = stat.split(':')
2137 if key == k:
2138 if value is None and kind is None:
2139 return
2140 elif value:
2141 if value == v:
2142 return
2143 elif kind:
2144 if v.endswith('|' + kind):
2145 return
2146 time.sleep(0.1)
2147
Clark Boylanb640e052014-04-03 16:41:46 -07002148 raise Exception("Key %s not found in reported stats" % key)
James E. Blair59fdbac2015-12-07 17:08:06 -08002149
James E. Blair2b2a8ab2016-08-11 14:39:11 -07002150 def assertBuilds(self, builds):
2151 """Assert that the running builds are as described.
2152
2153 The list of running builds is examined and must match exactly
2154 the list of builds described by the input.
2155
2156 :arg list builds: A list of dictionaries. Each item in the
2157 list must match the corresponding build in the build
2158 history, and each element of the dictionary must match the
2159 corresponding attribute of the build.
2160
2161 """
James E. Blair3158e282016-08-19 09:34:11 -07002162 try:
2163 self.assertEqual(len(self.builds), len(builds))
2164 for i, d in enumerate(builds):
2165 for k, v in d.items():
2166 self.assertEqual(
2167 getattr(self.builds[i], k), v,
2168 "Element %i in builds does not match" % (i,))
2169 except Exception:
2170 for build in self.builds:
2171 self.log.error("Running build: %s" % build)
2172 else:
2173 self.log.error("No running builds")
2174 raise
James E. Blair2b2a8ab2016-08-11 14:39:11 -07002175
James E. Blairb536ecc2016-08-31 10:11:42 -07002176 def assertHistory(self, history, ordered=True):
James E. Blair2b2a8ab2016-08-11 14:39:11 -07002177 """Assert that the completed builds are as described.
2178
2179 The list of completed builds is examined and must match
2180 exactly the list of builds described by the input.
2181
2182 :arg list history: A list of dictionaries. Each item in the
2183 list must match the corresponding build in the build
2184 history, and each element of the dictionary must match the
2185 corresponding attribute of the build.
2186
James E. Blairb536ecc2016-08-31 10:11:42 -07002187 :arg bool ordered: If true, the history must match the order
2188 supplied, if false, the builds are permitted to have
2189 arrived in any order.
2190
James E. Blair2b2a8ab2016-08-11 14:39:11 -07002191 """
James E. Blairb536ecc2016-08-31 10:11:42 -07002192 def matches(history_item, item):
2193 for k, v in item.items():
2194 if getattr(history_item, k) != v:
2195 return False
2196 return True
James E. Blair3158e282016-08-19 09:34:11 -07002197 try:
2198 self.assertEqual(len(self.history), len(history))
James E. Blairb536ecc2016-08-31 10:11:42 -07002199 if ordered:
2200 for i, d in enumerate(history):
2201 if not matches(self.history[i], d):
2202 raise Exception(
K Jonathan Harkercc3a6f02017-02-22 19:08:06 -08002203 "Element %i in history does not match %s" %
2204 (i, self.history[i]))
James E. Blairb536ecc2016-08-31 10:11:42 -07002205 else:
2206 unseen = self.history[:]
2207 for i, d in enumerate(history):
2208 found = False
2209 for unseen_item in unseen:
2210 if matches(unseen_item, d):
2211 found = True
2212 unseen.remove(unseen_item)
2213 break
2214 if not found:
2215 raise Exception("No match found for element %i "
2216 "in history" % (i,))
2217 if unseen:
2218 raise Exception("Unexpected items in history")
James E. Blair3158e282016-08-19 09:34:11 -07002219 except Exception:
2220 for build in self.history:
2221 self.log.error("Completed build: %s" % build)
2222 else:
2223 self.log.error("No completed builds")
2224 raise
James E. Blair2b2a8ab2016-08-11 14:39:11 -07002225
James E. Blair6ac368c2016-12-22 18:07:20 -08002226 def printHistory(self):
2227 """Log the build history.
2228
2229 This can be useful during tests to summarize what jobs have
2230 completed.
2231
2232 """
2233 self.log.debug("Build history:")
2234 for build in self.history:
2235 self.log.debug(build)
2236
James E. Blair59fdbac2015-12-07 17:08:06 -08002237 def getPipeline(self, name):
James E. Blairf84026c2015-12-08 16:11:46 -08002238 return self.sched.abide.tenants.values()[0].layout.pipelines.get(name)
2239
James E. Blair9ea70072017-04-19 16:05:30 -07002240 def updateConfigLayout(self, path):
James E. Blairf84026c2015-12-08 16:11:46 -08002241 root = os.path.join(self.test_root, "config")
Clint Byrumead6c562017-02-01 16:34:04 -08002242 if not os.path.exists(root):
2243 os.makedirs(root)
James E. Blairf84026c2015-12-08 16:11:46 -08002244 f = tempfile.NamedTemporaryFile(dir=root, delete=False)
2245 f.write("""
Paul Belanger66e95962016-11-11 12:11:06 -05002246- tenant:
2247 name: openstack
2248 source:
2249 gerrit:
James E. Blair109da3f2017-04-04 14:39:43 -07002250 config-projects:
Paul Belanger66e95962016-11-11 12:11:06 -05002251 - %s
James E. Blair109da3f2017-04-04 14:39:43 -07002252 untrusted-projects:
James E. Blair0ffa0102017-03-30 13:11:33 -07002253 - org/project
2254 - org/project1
James E. Blair7cb84542017-04-19 13:35:05 -07002255 - org/project2\n""" % path)
James E. Blairf84026c2015-12-08 16:11:46 -08002256 f.close()
Paul Belanger66e95962016-11-11 12:11:06 -05002257 self.config.set('zuul', 'tenant_config',
2258 os.path.join(FIXTURE_DIR, f.name))
Ricardo Carrillo Cruz22994f92016-12-02 11:41:58 +00002259 self.setupAllProjectKeys()
James E. Blair14abdf42015-12-09 16:11:53 -08002260
James E. Blair8b1dc3f2016-07-05 16:49:00 -07002261 def addCommitToRepo(self, project, message, files,
2262 branch='master', tag=None):
James E. Blair14abdf42015-12-09 16:11:53 -08002263 path = os.path.join(self.upstream_root, project)
2264 repo = git.Repo(path)
2265 repo.head.reference = branch
2266 zuul.merger.merger.reset_repo_to_head(repo)
2267 for fn, content in files.items():
2268 fn = os.path.join(path, fn)
James E. Blairc73c73a2017-01-20 15:15:15 -08002269 try:
2270 os.makedirs(os.path.dirname(fn))
2271 except OSError:
2272 pass
James E. Blair14abdf42015-12-09 16:11:53 -08002273 with open(fn, 'w') as f:
2274 f.write(content)
2275 repo.index.add([fn])
2276 commit = repo.index.commit(message)
Clint Byrum58264dc2017-02-07 21:21:22 -08002277 before = repo.heads[branch].commit
James E. Blair14abdf42015-12-09 16:11:53 -08002278 repo.heads[branch].commit = commit
2279 repo.head.reference = branch
2280 repo.git.clean('-x', '-f', '-d')
2281 repo.heads[branch].checkout()
James E. Blair8b1dc3f2016-07-05 16:49:00 -07002282 if tag:
2283 repo.create_tag(tag)
Clint Byrum58264dc2017-02-07 21:21:22 -08002284 return before
2285
James E. Blair9ea0d0b2017-04-20 09:27:15 -07002286 def commitConfigUpdate(self, project_name, source_name):
2287 """Commit an update to zuul.yaml
2288
2289 This overwrites the zuul.yaml in the specificed project with
2290 the contents specified.
2291
2292 :arg str project_name: The name of the project containing
2293 zuul.yaml (e.g., common-config)
2294
2295 :arg str source_name: The path to the file (underneath the
2296 test fixture directory) whose contents should be used to
2297 replace zuul.yaml.
2298 """
2299
2300 source_path = os.path.join(FIXTURE_DIR, source_name)
James E. Blairdfdfcfc2017-04-20 10:19:20 -07002301 files = {}
2302 with open(source_path, 'r') as f:
2303 data = f.read()
2304 layout = yaml.safe_load(data)
2305 files['zuul.yaml'] = data
2306 for item in layout:
2307 if 'job' in item:
2308 jobname = item['job']['name']
2309 files['playbooks/%s.yaml' % jobname] = ''
James E. Blair9ea0d0b2017-04-20 09:27:15 -07002310 before = self.addCommitToRepo(
2311 project_name, 'Pulling content from %s' % source_name,
James E. Blairdfdfcfc2017-04-20 10:19:20 -07002312 files)
James E. Blair9ea0d0b2017-04-20 09:27:15 -07002313 return before
2314
James E. Blair7fc8daa2016-08-08 15:37:15 -07002315 def addEvent(self, connection, event):
James E. Blair9ea0d0b2017-04-20 09:27:15 -07002316
James E. Blair7fc8daa2016-08-08 15:37:15 -07002317 """Inject a Fake (Gerrit) event.
2318
2319 This method accepts a JSON-encoded event and simulates Zuul
2320 having received it from Gerrit. It could (and should)
2321 eventually apply to any connection type, but is currently only
2322 used with Gerrit connections. The name of the connection is
2323 used to look up the corresponding server, and the event is
2324 simulated as having been received by all Zuul connections
2325 attached to that server. So if two Gerrit connections in Zuul
2326 are connected to the same Gerrit server, and you invoke this
2327 method specifying the name of one of them, the event will be
2328 received by both.
2329
2330 .. note::
2331
2332 "self.fake_gerrit.addEvent" calls should be migrated to
2333 this method.
2334
2335 :arg str connection: The name of the connection corresponding
Clark Boylan500992b2017-04-03 14:28:24 -07002336 to the gerrit server.
James E. Blair7fc8daa2016-08-08 15:37:15 -07002337 :arg str event: The JSON-encoded event.
2338
2339 """
2340 specified_conn = self.connections.connections[connection]
2341 for conn in self.connections.connections.values():
2342 if (isinstance(conn, specified_conn.__class__) and
2343 specified_conn.server == conn.server):
2344 conn.addEvent(event)
2345
James E. Blair3f876d52016-07-22 13:07:14 -07002346
2347class AnsibleZuulTestCase(ZuulTestCase):
Paul Belanger174a8272017-03-14 13:20:10 -04002348 """ZuulTestCase but with an actual ansible executor running"""
James E. Blaire1767bc2016-08-02 10:00:27 -07002349 run_ansible = True
Joshua Hesketh25695cb2017-03-06 12:50:04 +11002350
Joshua Heskethd78b4482015-09-14 16:56:34 -06002351
2352class ZuulDBTestCase(ZuulTestCase):
James E. Blair82844892017-03-06 10:55:26 -08002353 def setup_config(self):
2354 super(ZuulDBTestCase, self).setup_config()
Joshua Heskethd78b4482015-09-14 16:56:34 -06002355 for section_name in self.config.sections():
2356 con_match = re.match(r'^connection ([\'\"]?)(.*)(\1)$',
2357 section_name, re.I)
2358 if not con_match:
2359 continue
2360
2361 if self.config.get(section_name, 'driver') == 'sql':
2362 f = MySQLSchemaFixture()
2363 self.useFixture(f)
2364 if (self.config.get(section_name, 'dburi') ==
2365 '$MYSQL_FIXTURE_DBURI$'):
2366 self.config.set(section_name, 'dburi', f.dburi)