blob: bdf4bbdb0d8edb26af70b5d2a1282d23c168afb2 [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.
4#
5# Licensed under the Apache License, Version 2.0 (the "License"); you may
6# not use this file except in compliance with the License. You may obtain
7# a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14# License for the specific language governing permissions and limitations
15# under the License.
16
Christian Berendtffba5df2014-06-07 21:30:22 +020017from six.moves import configparser as ConfigParser
Clark Boylanb640e052014-04-03 16:41:46 -070018import gc
19import hashlib
20import json
21import logging
22import os
23import pprint
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 Boylanb640e052014-04-03 16:41:46 -070031import socket
32import string
33import subprocess
34import swiftclient
James E. Blairf84026c2015-12-08 16:11:46 -080035import tempfile
Clark Boylanb640e052014-04-03 16:41:46 -070036import threading
37import time
Clark Boylanb640e052014-04-03 16:41:46 -070038
39import git
40import gear
41import fixtures
Clark Boylanb640e052014-04-03 16:41:46 -070042import statsd
43import testtools
Mike Heald8225f522014-11-21 09:52:33 +000044from git import GitCommandError
Clark Boylanb640e052014-04-03 16:41:46 -070045
Joshua Hesketh352264b2015-08-11 23:42:08 +100046import zuul.connection.gerrit
47import zuul.connection.smtp
Clark Boylanb640e052014-04-03 16:41:46 -070048import zuul.scheduler
49import zuul.webapp
50import zuul.rpclistener
Joshua Hesketh0c54b2a2016-04-11 21:23:33 +100051import zuul.launcher.server
52import zuul.launcher.client
Clark Boylanb640e052014-04-03 16:41:46 -070053import zuul.lib.swift
James E. Blair83005782015-12-11 14:46:03 -080054import zuul.lib.connections
Clark Boylanb640e052014-04-03 16:41:46 -070055import zuul.merger.client
James E. Blair879dafb2015-07-17 14:04:49 -070056import zuul.merger.merger
57import zuul.merger.server
James E. Blair8d692392016-04-08 17:47:58 -070058import zuul.nodepool
Clark Boylanb640e052014-04-03 16:41:46 -070059import zuul.reporter.gerrit
60import zuul.reporter.smtp
Joshua Hesketh850ccb62014-11-27 11:31:02 +110061import zuul.source.gerrit
Clark Boylanb640e052014-04-03 16:41:46 -070062import zuul.trigger.gerrit
63import zuul.trigger.timer
James E. Blairc494d542014-08-06 09:23:52 -070064import zuul.trigger.zuultrigger
Clark Boylanb640e052014-04-03 16:41:46 -070065
66FIXTURE_DIR = os.path.join(os.path.dirname(__file__),
67 'fixtures')
James E. Blair97d902e2014-08-21 13:25:56 -070068USE_TEMPDIR = True
Clark Boylanb640e052014-04-03 16:41:46 -070069
70logging.basicConfig(level=logging.DEBUG,
71 format='%(asctime)s %(name)-32s '
72 '%(levelname)-8s %(message)s')
73
74
75def repack_repo(path):
76 cmd = ['git', '--git-dir=%s/.git' % path, 'repack', '-afd']
77 output = subprocess.Popen(cmd, close_fds=True,
78 stdout=subprocess.PIPE,
79 stderr=subprocess.PIPE)
80 out = output.communicate()
81 if output.returncode:
82 raise Exception("git repack returned %d" % output.returncode)
83 return out
84
85
86def random_sha1():
87 return hashlib.sha1(str(random.random())).hexdigest()
88
89
James E. Blaira190f3b2015-01-05 14:56:54 -080090def iterate_timeout(max_seconds, purpose):
91 start = time.time()
92 count = 0
93 while (time.time() < start + max_seconds):
94 count += 1
95 yield count
96 time.sleep(0)
97 raise Exception("Timeout waiting for %s" % purpose)
98
99
Clark Boylanb640e052014-04-03 16:41:46 -0700100class ChangeReference(git.Reference):
101 _common_path_default = "refs/changes"
102 _points_to_commits_only = True
103
104
105class FakeChange(object):
106 categories = {'APRV': ('Approved', -1, 1),
107 'CRVW': ('Code-Review', -2, 2),
108 'VRFY': ('Verified', -2, 2)}
109
110 def __init__(self, gerrit, number, project, branch, subject,
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700111 status='NEW', upstream_root=None, files={}):
Clark Boylanb640e052014-04-03 16:41:46 -0700112 self.gerrit = gerrit
113 self.reported = 0
114 self.queried = 0
115 self.patchsets = []
116 self.number = number
117 self.project = project
118 self.branch = branch
119 self.subject = subject
120 self.latest_patchset = 0
121 self.depends_on_change = None
122 self.needed_by_changes = []
123 self.fail_merge = False
124 self.messages = []
125 self.data = {
126 'branch': branch,
127 'comments': [],
128 'commitMessage': subject,
129 'createdOn': time.time(),
130 'id': 'I' + random_sha1(),
131 'lastUpdated': time.time(),
132 'number': str(number),
133 'open': status == 'NEW',
134 'owner': {'email': 'user@example.com',
135 'name': 'User Name',
136 'username': 'username'},
137 'patchSets': self.patchsets,
138 'project': project,
139 'status': status,
140 'subject': subject,
141 'submitRecords': [],
142 'url': 'https://hostname/%s' % number}
143
144 self.upstream_root = upstream_root
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700145 self.addPatchset(files=files)
Clark Boylanb640e052014-04-03 16:41:46 -0700146 self.data['submitRecords'] = self.getSubmitRecords()
147 self.open = status == 'NEW'
148
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700149 def addFakeChangeToRepo(self, msg, files, large):
Clark Boylanb640e052014-04-03 16:41:46 -0700150 path = os.path.join(self.upstream_root, self.project)
151 repo = git.Repo(path)
152 ref = ChangeReference.create(repo, '1/%s/%s' % (self.number,
153 self.latest_patchset),
154 'refs/tags/init')
155 repo.head.reference = ref
James E. Blair879dafb2015-07-17 14:04:49 -0700156 zuul.merger.merger.reset_repo_to_head(repo)
Clark Boylanb640e052014-04-03 16:41:46 -0700157 repo.git.clean('-x', '-f', '-d')
158
159 path = os.path.join(self.upstream_root, self.project)
160 if not large:
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700161 for fn, content in files.items():
162 fn = os.path.join(path, fn)
163 with open(fn, 'w') as f:
164 f.write(content)
165 repo.index.add([fn])
Clark Boylanb640e052014-04-03 16:41:46 -0700166 else:
167 for fni in range(100):
168 fn = os.path.join(path, str(fni))
169 f = open(fn, 'w')
170 for ci in range(4096):
171 f.write(random.choice(string.printable))
172 f.close()
173 repo.index.add([fn])
174
175 r = repo.index.commit(msg)
176 repo.head.reference = 'master'
James E. Blair879dafb2015-07-17 14:04:49 -0700177 zuul.merger.merger.reset_repo_to_head(repo)
Clark Boylanb640e052014-04-03 16:41:46 -0700178 repo.git.clean('-x', '-f', '-d')
179 repo.heads['master'].checkout()
180 return r
181
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700182 def addPatchset(self, files=None, large=False):
Clark Boylanb640e052014-04-03 16:41:46 -0700183 self.latest_patchset += 1
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700184 if not files:
James E. Blair97d902e2014-08-21 13:25:56 -0700185 fn = '%s-%s' % (self.branch.replace('/', '_'), self.number)
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700186 data = ("test %s %s %s\n" %
187 (self.branch, self.number, self.latest_patchset))
188 files = {fn: data}
Clark Boylanb640e052014-04-03 16:41:46 -0700189 msg = self.subject + '-' + str(self.latest_patchset)
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700190 c = self.addFakeChangeToRepo(msg, files, large)
Clark Boylanb640e052014-04-03 16:41:46 -0700191 ps_files = [{'file': '/COMMIT_MSG',
192 'type': 'ADDED'},
193 {'file': 'README',
194 'type': 'MODIFIED'}]
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700195 for f in files.keys():
Clark Boylanb640e052014-04-03 16:41:46 -0700196 ps_files.append({'file': f, 'type': 'ADDED'})
197 d = {'approvals': [],
198 'createdOn': time.time(),
199 'files': ps_files,
200 'number': str(self.latest_patchset),
201 'ref': 'refs/changes/1/%s/%s' % (self.number,
202 self.latest_patchset),
203 'revision': c.hexsha,
204 'uploader': {'email': 'user@example.com',
205 'name': 'User name',
206 'username': 'user'}}
207 self.data['currentPatchSet'] = d
208 self.patchsets.append(d)
209 self.data['submitRecords'] = self.getSubmitRecords()
210
211 def getPatchsetCreatedEvent(self, patchset):
212 event = {"type": "patchset-created",
213 "change": {"project": self.project,
214 "branch": self.branch,
215 "id": "I5459869c07352a31bfb1e7a8cac379cabfcb25af",
216 "number": str(self.number),
217 "subject": self.subject,
218 "owner": {"name": "User Name"},
219 "url": "https://hostname/3"},
220 "patchSet": self.patchsets[patchset - 1],
221 "uploader": {"name": "User Name"}}
222 return event
223
224 def getChangeRestoredEvent(self):
225 event = {"type": "change-restored",
226 "change": {"project": self.project,
227 "branch": self.branch,
228 "id": "I5459869c07352a31bfb1e7a8cac379cabfcb25af",
229 "number": str(self.number),
230 "subject": self.subject,
231 "owner": {"name": "User Name"},
232 "url": "https://hostname/3"},
233 "restorer": {"name": "User Name"},
Antoine Mussobd86a312014-01-08 14:51:33 +0100234 "patchSet": self.patchsets[-1],
235 "reason": ""}
236 return event
237
238 def getChangeAbandonedEvent(self):
239 event = {"type": "change-abandoned",
240 "change": {"project": self.project,
241 "branch": self.branch,
242 "id": "I5459869c07352a31bfb1e7a8cac379cabfcb25af",
243 "number": str(self.number),
244 "subject": self.subject,
245 "owner": {"name": "User Name"},
246 "url": "https://hostname/3"},
247 "abandoner": {"name": "User Name"},
248 "patchSet": self.patchsets[-1],
Clark Boylanb640e052014-04-03 16:41:46 -0700249 "reason": ""}
250 return event
251
252 def getChangeCommentEvent(self, patchset):
253 event = {"type": "comment-added",
254 "change": {"project": self.project,
255 "branch": self.branch,
256 "id": "I5459869c07352a31bfb1e7a8cac379cabfcb25af",
257 "number": str(self.number),
258 "subject": self.subject,
259 "owner": {"name": "User Name"},
260 "url": "https://hostname/3"},
261 "patchSet": self.patchsets[patchset - 1],
262 "author": {"name": "User Name"},
263 "approvals": [{"type": "Code-Review",
264 "description": "Code-Review",
265 "value": "0"}],
266 "comment": "This is a comment"}
267 return event
268
Joshua Hesketh642824b2014-07-01 17:54:59 +1000269 def addApproval(self, category, value, username='reviewer_john',
270 granted_on=None, message=''):
Clark Boylanb640e052014-04-03 16:41:46 -0700271 if not granted_on:
272 granted_on = time.time()
Joshua Hesketh29d99b72014-08-19 16:27:42 +1000273 approval = {
274 'description': self.categories[category][0],
275 'type': category,
276 'value': str(value),
277 'by': {
278 'username': username,
279 'email': username + '@example.com',
280 },
281 'grantedOn': int(granted_on)
282 }
Clark Boylanb640e052014-04-03 16:41:46 -0700283 for i, x in enumerate(self.patchsets[-1]['approvals'][:]):
284 if x['by']['username'] == username and x['type'] == category:
285 del self.patchsets[-1]['approvals'][i]
286 self.patchsets[-1]['approvals'].append(approval)
287 event = {'approvals': [approval],
Joshua Hesketh642824b2014-07-01 17:54:59 +1000288 'author': {'email': 'author@example.com',
289 'name': 'Patchset Author',
290 'username': 'author_phil'},
Clark Boylanb640e052014-04-03 16:41:46 -0700291 'change': {'branch': self.branch,
292 'id': 'Iaa69c46accf97d0598111724a38250ae76a22c87',
293 'number': str(self.number),
Joshua Hesketh642824b2014-07-01 17:54:59 +1000294 'owner': {'email': 'owner@example.com',
295 'name': 'Change Owner',
296 'username': 'owner_jane'},
Clark Boylanb640e052014-04-03 16:41:46 -0700297 'project': self.project,
298 'subject': self.subject,
299 'topic': 'master',
300 'url': 'https://hostname/459'},
Joshua Hesketh642824b2014-07-01 17:54:59 +1000301 'comment': message,
Clark Boylanb640e052014-04-03 16:41:46 -0700302 'patchSet': self.patchsets[-1],
303 'type': 'comment-added'}
304 self.data['submitRecords'] = self.getSubmitRecords()
305 return json.loads(json.dumps(event))
306
307 def getSubmitRecords(self):
308 status = {}
309 for cat in self.categories.keys():
310 status[cat] = 0
311
312 for a in self.patchsets[-1]['approvals']:
313 cur = status[a['type']]
314 cat_min, cat_max = self.categories[a['type']][1:]
315 new = int(a['value'])
316 if new == cat_min:
317 cur = new
318 elif abs(new) > abs(cur):
319 cur = new
320 status[a['type']] = cur
321
322 labels = []
323 ok = True
324 for typ, cat in self.categories.items():
325 cur = status[typ]
326 cat_min, cat_max = cat[1:]
327 if cur == cat_min:
328 value = 'REJECT'
329 ok = False
330 elif cur == cat_max:
331 value = 'OK'
332 else:
333 value = 'NEED'
334 ok = False
335 labels.append({'label': cat[0], 'status': value})
336 if ok:
337 return [{'status': 'OK'}]
338 return [{'status': 'NOT_READY',
339 'labels': labels}]
340
341 def setDependsOn(self, other, patchset):
342 self.depends_on_change = other
343 d = {'id': other.data['id'],
344 'number': other.data['number'],
345 'ref': other.patchsets[patchset - 1]['ref']
346 }
347 self.data['dependsOn'] = [d]
348
349 other.needed_by_changes.append(self)
350 needed = other.data.get('neededBy', [])
351 d = {'id': self.data['id'],
352 'number': self.data['number'],
353 'ref': self.patchsets[patchset - 1]['ref'],
354 'revision': self.patchsets[patchset - 1]['revision']
355 }
356 needed.append(d)
357 other.data['neededBy'] = needed
358
359 def query(self):
360 self.queried += 1
361 d = self.data.get('dependsOn')
362 if d:
363 d = d[0]
364 if (self.depends_on_change.patchsets[-1]['ref'] == d['ref']):
365 d['isCurrentPatchSet'] = True
366 else:
367 d['isCurrentPatchSet'] = False
368 return json.loads(json.dumps(self.data))
369
370 def setMerged(self):
371 if (self.depends_on_change and
Joshua Hesketh29d99b72014-08-19 16:27:42 +1000372 self.depends_on_change.data['status'] != 'MERGED'):
Clark Boylanb640e052014-04-03 16:41:46 -0700373 return
374 if self.fail_merge:
375 return
376 self.data['status'] = 'MERGED'
377 self.open = False
378
379 path = os.path.join(self.upstream_root, self.project)
380 repo = git.Repo(path)
381 repo.heads[self.branch].commit = \
382 repo.commit(self.patchsets[-1]['revision'])
383
384 def setReported(self):
385 self.reported += 1
386
387
Joshua Hesketh352264b2015-08-11 23:42:08 +1000388class FakeGerritConnection(zuul.connection.gerrit.GerritConnection):
James E. Blaire7b99a02016-08-05 14:27:34 -0700389 """A Fake Gerrit connection for use in tests.
390
391 This subclasses
392 :py:class:`~zuul.connection.gerrit.GerritConnection` to add the
393 ability for tests to add changes to the fake Gerrit it represents.
394 """
395
Joshua Hesketh352264b2015-08-11 23:42:08 +1000396 log = logging.getLogger("zuul.test.FakeGerritConnection")
James E. Blair96698e22015-04-02 07:48:21 -0700397
Joshua Hesketh352264b2015-08-11 23:42:08 +1000398 def __init__(self, connection_name, connection_config,
Jan Hruban6b71aff2015-10-22 16:58:08 +0200399 changes_db=None, queues_db=None, upstream_root=None):
Joshua Hesketh352264b2015-08-11 23:42:08 +1000400 super(FakeGerritConnection, self).__init__(connection_name,
401 connection_config)
402
403 self.event_queue = queues_db
Clark Boylanb640e052014-04-03 16:41:46 -0700404 self.fixture_dir = os.path.join(FIXTURE_DIR, 'gerrit')
405 self.change_number = 0
Joshua Hesketh352264b2015-08-11 23:42:08 +1000406 self.changes = changes_db
James E. Blairf8ff9932014-08-15 15:24:24 -0700407 self.queries = []
Jan Hruban6b71aff2015-10-22 16:58:08 +0200408 self.upstream_root = upstream_root
Clark Boylanb640e052014-04-03 16:41:46 -0700409
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700410 def addFakeChange(self, project, branch, subject, status='NEW',
411 files=None):
James E. Blaire7b99a02016-08-05 14:27:34 -0700412 """Add a change to the fake Gerrit."""
Clark Boylanb640e052014-04-03 16:41:46 -0700413 self.change_number += 1
414 c = FakeChange(self, self.change_number, project, branch, subject,
415 upstream_root=self.upstream_root,
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700416 status=status, files=files)
Clark Boylanb640e052014-04-03 16:41:46 -0700417 self.changes[self.change_number] = c
418 return c
419
Clark Boylanb640e052014-04-03 16:41:46 -0700420 def review(self, project, changeid, message, action):
421 number, ps = changeid.split(',')
422 change = self.changes[int(number)]
Joshua Hesketh642824b2014-07-01 17:54:59 +1000423
424 # Add the approval back onto the change (ie simulate what gerrit would
425 # do).
426 # Usually when zuul leaves a review it'll create a feedback loop where
427 # zuul's review enters another gerrit event (which is then picked up by
428 # zuul). However, we can't mimic this behaviour (by adding this
429 # approval event into the queue) as it stops jobs from checking what
430 # happens before this event is triggered. If a job needs to see what
431 # happens they can add their own verified event into the queue.
432 # Nevertheless, we can update change with the new review in gerrit.
433
434 for cat in ['CRVW', 'VRFY', 'APRV']:
435 if cat in action:
Joshua Hesketh352264b2015-08-11 23:42:08 +1000436 change.addApproval(cat, action[cat], username=self.user)
Joshua Hesketh642824b2014-07-01 17:54:59 +1000437
438 if 'label' in action:
439 parts = action['label'].split('=')
Joshua Hesketh352264b2015-08-11 23:42:08 +1000440 change.addApproval(parts[0], parts[2], username=self.user)
Joshua Hesketh642824b2014-07-01 17:54:59 +1000441
Clark Boylanb640e052014-04-03 16:41:46 -0700442 change.messages.append(message)
Joshua Hesketh642824b2014-07-01 17:54:59 +1000443
Clark Boylanb640e052014-04-03 16:41:46 -0700444 if 'submit' in action:
445 change.setMerged()
446 if message:
447 change.setReported()
448
449 def query(self, number):
450 change = self.changes.get(int(number))
451 if change:
452 return change.query()
453 return {}
454
James E. Blairc494d542014-08-06 09:23:52 -0700455 def simpleQuery(self, query):
James E. Blair96698e22015-04-02 07:48:21 -0700456 self.log.debug("simpleQuery: %s" % query)
James E. Blairf8ff9932014-08-15 15:24:24 -0700457 self.queries.append(query)
James E. Blair5ee24252014-12-30 10:12:29 -0800458 if query.startswith('change:'):
459 # Query a specific changeid
460 changeid = query[len('change:'):]
461 l = [change.query() for change in self.changes.values()
462 if change.data['id'] == changeid]
James E. Blair96698e22015-04-02 07:48:21 -0700463 elif query.startswith('message:'):
464 # Query the content of a commit message
465 msg = query[len('message:'):].strip()
466 l = [change.query() for change in self.changes.values()
467 if msg in change.data['commitMessage']]
James E. Blair5ee24252014-12-30 10:12:29 -0800468 else:
469 # Query all open changes
470 l = [change.query() for change in self.changes.values()]
James E. Blairf8ff9932014-08-15 15:24:24 -0700471 return l
James E. Blairc494d542014-08-06 09:23:52 -0700472
Joshua Hesketh352264b2015-08-11 23:42:08 +1000473 def _start_watcher_thread(self, *args, **kw):
Clark Boylanb640e052014-04-03 16:41:46 -0700474 pass
475
Joshua Hesketh352264b2015-08-11 23:42:08 +1000476 def getGitUrl(self, project):
477 return os.path.join(self.upstream_root, project.name)
478
Clark Boylanb640e052014-04-03 16:41:46 -0700479
480class BuildHistory(object):
481 def __init__(self, **kw):
482 self.__dict__.update(kw)
483
484 def __repr__(self):
485 return ("<Completed build, result: %s name: %s #%s changes: %s>" %
486 (self.result, self.name, self.number, self.changes))
487
488
489class FakeURLOpener(object):
Jan Hruban6b71aff2015-10-22 16:58:08 +0200490 def __init__(self, upstream_root, url):
Clark Boylanb640e052014-04-03 16:41:46 -0700491 self.upstream_root = upstream_root
Clark Boylanb640e052014-04-03 16:41:46 -0700492 self.url = url
493
494 def read(self):
Morgan Fainberg293f7f82016-05-30 14:01:22 -0700495 res = urllib.parse.urlparse(self.url)
Clark Boylanb640e052014-04-03 16:41:46 -0700496 path = res.path
497 project = '/'.join(path.split('/')[2:-2])
498 ret = '001e# service=git-upload-pack\n'
499 ret += ('000000a31270149696713ba7e06f1beb760f20d359c4abed HEAD\x00'
500 'multi_ack thin-pack side-band side-band-64k ofs-delta '
501 'shallow no-progress include-tag multi_ack_detailed no-done\n')
502 path = os.path.join(self.upstream_root, project)
503 repo = git.Repo(path)
504 for ref in repo.refs:
505 r = ref.object.hexsha + ' ' + ref.path + '\n'
506 ret += '%04x%s' % (len(r) + 4, r)
507 ret += '0000'
508 return ret
509
510
Clark Boylanb640e052014-04-03 16:41:46 -0700511class FakeStatsd(threading.Thread):
512 def __init__(self):
513 threading.Thread.__init__(self)
514 self.daemon = True
515 self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
516 self.sock.bind(('', 0))
517 self.port = self.sock.getsockname()[1]
518 self.wake_read, self.wake_write = os.pipe()
519 self.stats = []
520
521 def run(self):
522 while True:
523 poll = select.poll()
524 poll.register(self.sock, select.POLLIN)
525 poll.register(self.wake_read, select.POLLIN)
526 ret = poll.poll()
527 for (fd, event) in ret:
528 if fd == self.sock.fileno():
529 data = self.sock.recvfrom(1024)
530 if not data:
531 return
532 self.stats.append(data[0])
533 if fd == self.wake_read:
534 return
535
536 def stop(self):
537 os.write(self.wake_write, '1\n')
538
539
James E. Blaire1767bc2016-08-02 10:00:27 -0700540class FakeBuild(object):
Clark Boylanb640e052014-04-03 16:41:46 -0700541 log = logging.getLogger("zuul.test")
542
James E. Blairab7132b2016-08-05 12:36:22 -0700543 def __init__(self, launch_server, job, number, node):
Clark Boylanb640e052014-04-03 16:41:46 -0700544 self.daemon = True
James E. Blaire1767bc2016-08-02 10:00:27 -0700545 self.launch_server = launch_server
Clark Boylanb640e052014-04-03 16:41:46 -0700546 self.job = job
James E. Blairab7132b2016-08-05 12:36:22 -0700547 self.jobdir = None
Clark Boylanb640e052014-04-03 16:41:46 -0700548 self.number = number
549 self.node = node
550 self.parameters = json.loads(job.arguments)
551 self.unique = self.parameters['ZUUL_UUID']
James E. Blair3f876d52016-07-22 13:07:14 -0700552 self.name = self.parameters['job']
Clark Boylanb640e052014-04-03 16:41:46 -0700553 self.wait_condition = threading.Condition()
554 self.waiting = False
555 self.aborted = False
556 self.created = time.time()
Clark Boylanb640e052014-04-03 16:41:46 -0700557 self.run_error = False
James E. Blaire1767bc2016-08-02 10:00:27 -0700558 self.changes = None
559 if 'ZUUL_CHANGE_IDS' in self.parameters:
560 self.changes = self.parameters['ZUUL_CHANGE_IDS']
Clark Boylanb640e052014-04-03 16:41:46 -0700561
562 def release(self):
James E. Blaire7b99a02016-08-05 14:27:34 -0700563 """Release this build."""
Clark Boylanb640e052014-04-03 16:41:46 -0700564 self.wait_condition.acquire()
565 self.wait_condition.notify()
566 self.waiting = False
567 self.log.debug("Build %s released" % self.unique)
568 self.wait_condition.release()
569
570 def isWaiting(self):
James E. Blaire7b99a02016-08-05 14:27:34 -0700571 """Return whether this build is being held.
572
573 :returns: Whether the build is being held.
574 :rtype: bool
575 """
576
Clark Boylanb640e052014-04-03 16:41:46 -0700577 self.wait_condition.acquire()
578 if self.waiting:
579 ret = True
580 else:
581 ret = False
582 self.wait_condition.release()
583 return ret
584
585 def _wait(self):
586 self.wait_condition.acquire()
587 self.waiting = True
588 self.log.debug("Build %s waiting" % self.unique)
589 self.wait_condition.wait()
590 self.wait_condition.release()
591
592 def run(self):
593 data = {
594 'url': 'https://server/job/%s/%s/' % (self.name, self.number),
595 'name': self.name,
596 'number': self.number,
James E. Blaire1767bc2016-08-02 10:00:27 -0700597 'manager': self.launch_server.worker.worker_id,
Clark Boylanb640e052014-04-03 16:41:46 -0700598 'worker_name': 'My Worker',
599 'worker_hostname': 'localhost',
600 'worker_ips': ['127.0.0.1', '192.168.1.1'],
601 'worker_fqdn': 'zuul.example.org',
602 'worker_program': 'FakeBuilder',
603 'worker_version': 'v1.1',
604 'worker_extra': {'something': 'else'}
605 }
606
607 self.log.debug('Running build %s' % self.unique)
608
609 self.job.sendWorkData(json.dumps(data))
610 self.log.debug('Sent WorkData packet with %s' % json.dumps(data))
611 self.job.sendWorkStatus(0, 100)
612
James E. Blaire1767bc2016-08-02 10:00:27 -0700613 if self.launch_server.hold_jobs_in_build:
Clark Boylanb640e052014-04-03 16:41:46 -0700614 self.log.debug('Holding build %s' % self.unique)
615 self._wait()
616 self.log.debug("Build %s continuing" % self.unique)
617
Clark Boylanb640e052014-04-03 16:41:46 -0700618 result = 'SUCCESS'
619 if (('ZUUL_REF' in self.parameters) and
James E. Blaire1767bc2016-08-02 10:00:27 -0700620 self.launch_server.shouldFailTest(self.name,
621 self.parameters['ZUUL_REF'])):
Clark Boylanb640e052014-04-03 16:41:46 -0700622 result = 'FAILURE'
623 if self.aborted:
624 result = 'ABORTED'
625
626 if self.run_error:
Clark Boylanb640e052014-04-03 16:41:46 -0700627 result = 'RUN_ERROR'
Clark Boylanb640e052014-04-03 16:41:46 -0700628
James E. Blaire1767bc2016-08-02 10:00:27 -0700629 return result
Clark Boylanb640e052014-04-03 16:41:46 -0700630
James E. Blaire7b99a02016-08-05 14:27:34 -0700631 def hasChanges(self, *changes):
632 """Return whether this build has certain changes in its git repos.
633
634 :arg FakeChange changes: One or more changes (varargs) that
635 are expected to be present (in order) in the git repository of
636 the active project.
637
638 :returns: Whether the build has the indicated changes.
639 :rtype: bool
640
641 """
James E. Blair962220f2016-08-03 11:22:38 -0700642 project = self.parameters['ZUUL_PROJECT']
643 path = os.path.join(self.jobdir.git_root, project)
644 repo = git.Repo(path)
645 ref = self.parameters['ZUUL_REF']
646 repo_messages = [c.message.strip() for c in repo.iter_commits(ref)]
James E. Blaire7b99a02016-08-05 14:27:34 -0700647 commit_messages = ['%s-1' % change.subject for change in changes]
James E. Blair962220f2016-08-03 11:22:38 -0700648 self.log.debug("Checking if build %s has changes; commit_messages %s;"
649 " repo_messages %s" % (self, commit_messages,
650 repo_messages))
651 for msg in commit_messages:
652 if msg not in repo_messages:
653 self.log.debug(" messages do not match")
654 return False
655 self.log.debug(" OK")
656 return True
657
Clark Boylanb640e052014-04-03 16:41:46 -0700658
Joshua Hesketh0c54b2a2016-04-11 21:23:33 +1000659class RecordingLaunchServer(zuul.launcher.server.LaunchServer):
James E. Blaire7b99a02016-08-05 14:27:34 -0700660 """An Ansible launcher to be used in tests.
661
662 :ivar bool hold_jobs_in_build: If true, when jobs are launched
663 they will report that they have started but then pause until
664 released before reporting completion. This attribute may be
665 changed at any time and will take effect for subsequently
666 launched builds, but previously held builds will still need to
667 be explicitly released.
668
669 """
James E. Blairf5dbd002015-12-23 15:26:17 -0800670 def __init__(self, *args, **kw):
James E. Blaire1767bc2016-08-02 10:00:27 -0700671 self._run_ansible = kw.pop('_run_ansible', False)
James E. Blairf5dbd002015-12-23 15:26:17 -0800672 super(RecordingLaunchServer, self).__init__(*args, **kw)
James E. Blaire1767bc2016-08-02 10:00:27 -0700673 self.hold_jobs_in_build = False
674 self.lock = threading.Lock()
675 self.running_builds = []
James E. Blair3f876d52016-07-22 13:07:14 -0700676 self.build_history = []
James E. Blaire1767bc2016-08-02 10:00:27 -0700677 self._build_counter_lock = threading.Lock()
678 self.build_counter = 0
679 self.fail_tests = {}
James E. Blairab7132b2016-08-05 12:36:22 -0700680 self.job_builds = {}
James E. Blairf5dbd002015-12-23 15:26:17 -0800681
James E. Blaire1767bc2016-08-02 10:00:27 -0700682 def addFailTest(self, name, change):
James E. Blaire7b99a02016-08-05 14:27:34 -0700683 """Instruct the launcher to report matching builds as failures.
684
685 :arg str name: The name of the job to fail.
686 :arg change: TODO: document
687
688 """
James E. Blaire1767bc2016-08-02 10:00:27 -0700689 l = self.fail_tests.get(name, [])
690 l.append(change)
691 self.fail_tests[name] = l
James E. Blairf5dbd002015-12-23 15:26:17 -0800692
James E. Blaire1767bc2016-08-02 10:00:27 -0700693 def shouldFailTest(self, name, ref):
694 l = self.fail_tests.get(name, [])
695 for change in l:
696 if self.test.ref_has_change(ref, change):
697 return True
698 return False
James E. Blairf5dbd002015-12-23 15:26:17 -0800699
James E. Blair962220f2016-08-03 11:22:38 -0700700 def release(self, regex=None):
James E. Blaire7b99a02016-08-05 14:27:34 -0700701 """Release a held build.
702
703 :arg str regex: A regular expression which, if supplied, will
704 cause only builds with matching names to be released. If
705 not supplied, all builds will be released.
706
707 """
James E. Blair962220f2016-08-03 11:22:38 -0700708 builds = self.running_builds[:]
709 self.log.debug("Releasing build %s (%s)" % (regex,
710 len(self.running_builds)))
711 for build in builds:
712 if not regex or re.match(regex, build.name):
713 self.log.debug("Releasing build %s" %
714 (build.parameters['ZUUL_UUID']))
715 build.release()
716 else:
717 self.log.debug("Not releasing build %s" %
718 (build.parameters['ZUUL_UUID']))
719 self.log.debug("Done releasing builds %s (%s)" %
720 (regex, len(self.running_builds)))
721
James E. Blairab7132b2016-08-05 12:36:22 -0700722 def launch(self, job):
James E. Blaire1767bc2016-08-02 10:00:27 -0700723 with self._build_counter_lock:
724 self.build_counter += 1
725 build_counter = self.build_counter
726 node = None
James E. Blairab7132b2016-08-05 12:36:22 -0700727 build = FakeBuild(self, job, build_counter, node)
James E. Blaire1767bc2016-08-02 10:00:27 -0700728 job.build = build
James E. Blaire1767bc2016-08-02 10:00:27 -0700729 self.running_builds.append(build)
James E. Blairab7132b2016-08-05 12:36:22 -0700730 self.job_builds[job.unique] = build
731 super(RecordingLaunchServer, self).launch(job)
732
733 def runAnsible(self, jobdir, job):
734 build = self.job_builds[job.unique]
735 build.jobdir = jobdir
James E. Blaire1767bc2016-08-02 10:00:27 -0700736
737 if self._run_ansible:
738 result = super(RecordingLaunchServer, self).runAnsible(jobdir, job)
739 else:
740 result = build.run()
741
742 self.lock.acquire()
743 self.build_history.append(
744 BuildHistory(name=build.name, number=build.number,
745 result=result, changes=build.changes, node=build.node,
746 uuid=build.unique, parameters=build.parameters,
747 pipeline=build.parameters['ZUUL_PIPELINE'])
748 )
James E. Blairab7132b2016-08-05 12:36:22 -0700749 self.running_builds.remove(build)
750 del self.job_builds[job.unique]
James E. Blaire1767bc2016-08-02 10:00:27 -0700751 self.lock.release()
752 return result
James E. Blairf5dbd002015-12-23 15:26:17 -0800753
754
Clark Boylanb640e052014-04-03 16:41:46 -0700755class FakeGearmanServer(gear.Server):
James E. Blaire7b99a02016-08-05 14:27:34 -0700756 """A Gearman server for use in tests.
757
758 :ivar bool hold_jobs_in_queue: If true, submitted jobs will be
759 added to the queue but will not be distributed to workers
760 until released. This attribute may be changed at any time and
761 will take effect for subsequently enqueued jobs, but
762 previously held jobs will still need to be explicitly
763 released.
764
765 """
766
Clark Boylanb640e052014-04-03 16:41:46 -0700767 def __init__(self):
768 self.hold_jobs_in_queue = False
769 super(FakeGearmanServer, self).__init__(0)
770
771 def getJobForConnection(self, connection, peek=False):
772 for queue in [self.high_queue, self.normal_queue, self.low_queue]:
773 for job in queue:
774 if not hasattr(job, 'waiting'):
775 if job.name.startswith('build:'):
776 job.waiting = self.hold_jobs_in_queue
777 else:
778 job.waiting = False
779 if job.waiting:
780 continue
781 if job.name in connection.functions:
782 if not peek:
783 queue.remove(job)
784 connection.related_jobs[job.handle] = job
785 job.worker_connection = connection
786 job.running = True
787 return job
788 return None
789
790 def release(self, regex=None):
James E. Blaire7b99a02016-08-05 14:27:34 -0700791 """Release a held job.
792
793 :arg str regex: A regular expression which, if supplied, will
794 cause only jobs with matching names to be released. If
795 not supplied, all jobs will be released.
796 """
Clark Boylanb640e052014-04-03 16:41:46 -0700797 released = False
798 qlen = (len(self.high_queue) + len(self.normal_queue) +
799 len(self.low_queue))
800 self.log.debug("releasing queued job %s (%s)" % (regex, qlen))
801 for job in self.getQueue():
802 cmd, name = job.name.split(':')
803 if cmd != 'build':
804 continue
805 if not regex or re.match(regex, name):
806 self.log.debug("releasing queued job %s" %
807 job.unique)
808 job.waiting = False
809 released = True
810 else:
811 self.log.debug("not releasing queued job %s" %
812 job.unique)
813 if released:
814 self.wakeConnections()
815 qlen = (len(self.high_queue) + len(self.normal_queue) +
816 len(self.low_queue))
817 self.log.debug("done releasing queued jobs %s (%s)" % (regex, qlen))
818
819
820class FakeSMTP(object):
821 log = logging.getLogger('zuul.FakeSMTP')
822
823 def __init__(self, messages, server, port):
824 self.server = server
825 self.port = port
826 self.messages = messages
827
828 def sendmail(self, from_email, to_email, msg):
829 self.log.info("Sending email from %s, to %s, with msg %s" % (
830 from_email, to_email, msg))
831
832 headers = msg.split('\n\n', 1)[0]
833 body = msg.split('\n\n', 1)[1]
834
835 self.messages.append(dict(
836 from_email=from_email,
837 to_email=to_email,
838 msg=msg,
839 headers=headers,
840 body=body,
841 ))
842
843 return True
844
845 def quit(self):
846 return True
847
848
849class FakeSwiftClientConnection(swiftclient.client.Connection):
850 def post_account(self, headers):
851 # Do nothing
852 pass
853
854 def get_auth(self):
855 # Returns endpoint and (unused) auth token
856 endpoint = os.path.join('https://storage.example.org', 'V1',
857 'AUTH_account')
858 return endpoint, ''
859
860
Maru Newby3fe5f852015-01-13 04:22:14 +0000861class BaseTestCase(testtools.TestCase):
Clark Boylanb640e052014-04-03 16:41:46 -0700862 log = logging.getLogger("zuul.test")
863
864 def setUp(self):
Maru Newby3fe5f852015-01-13 04:22:14 +0000865 super(BaseTestCase, self).setUp()
Clark Boylanb640e052014-04-03 16:41:46 -0700866 test_timeout = os.environ.get('OS_TEST_TIMEOUT', 0)
867 try:
868 test_timeout = int(test_timeout)
869 except ValueError:
870 # If timeout value is invalid do not set a timeout.
871 test_timeout = 0
872 if test_timeout > 0:
873 self.useFixture(fixtures.Timeout(test_timeout, gentle=False))
874
875 if (os.environ.get('OS_STDOUT_CAPTURE') == 'True' or
876 os.environ.get('OS_STDOUT_CAPTURE') == '1'):
877 stdout = self.useFixture(fixtures.StringStream('stdout')).stream
878 self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout))
879 if (os.environ.get('OS_STDERR_CAPTURE') == 'True' or
880 os.environ.get('OS_STDERR_CAPTURE') == '1'):
881 stderr = self.useFixture(fixtures.StringStream('stderr')).stream
882 self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr))
883 if (os.environ.get('OS_LOG_CAPTURE') == 'True' or
884 os.environ.get('OS_LOG_CAPTURE') == '1'):
885 self.useFixture(fixtures.FakeLogger(
886 level=logging.DEBUG,
887 format='%(asctime)s %(name)-32s '
888 '%(levelname)-8s %(message)s'))
Maru Newby3fe5f852015-01-13 04:22:14 +0000889
Morgan Fainbergd34e0b42016-06-09 19:10:38 -0700890 # NOTE(notmorgan): Extract logging overrides for specific libraries
891 # from the OS_LOG_DEFAULTS env and create FakeLogger fixtures for
892 # each. This is used to limit the output during test runs from
893 # libraries that zuul depends on such as gear.
894 log_defaults_from_env = os.environ.get('OS_LOG_DEFAULTS')
895
896 if log_defaults_from_env:
897 for default in log_defaults_from_env.split(','):
898 try:
899 name, level_str = default.split('=', 1)
900 level = getattr(logging, level_str, logging.DEBUG)
901 self.useFixture(fixtures.FakeLogger(
902 name=name,
903 level=level,
904 format='%(asctime)s %(name)-32s '
905 '%(levelname)-8s %(message)s'))
906 except ValueError:
907 # NOTE(notmorgan): Invalid format of the log default,
908 # skip and don't try and apply a logger for the
909 # specified module
910 pass
911
Maru Newby3fe5f852015-01-13 04:22:14 +0000912
913class ZuulTestCase(BaseTestCase):
James E. Blaire7b99a02016-08-05 14:27:34 -0700914 """A test case with a functioning Zuul.
915
916 The following class variables are used during test setup and can
917 be overidden by subclasses but are effectively read-only once a
918 test method starts running:
919
920 :cvar str config_file: This points to the main zuul config file
921 within the fixtures directory. Subclasses may override this
922 to obtain a different behavior.
923
924 :cvar str tenant_config_file: This is the tenant config file
925 (which specifies from what git repos the configuration should
926 be loaded). It defaults to the value specified in
927 `config_file` but can be overidden by subclasses to obtain a
928 different tenant/project layout while using the standard main
929 configuration.
930
931 The following are instance variables that are useful within test
932 methods:
933
934 :ivar FakeGerritConnection fake_<connection>:
935 A :py:class:`~tests.base.FakeGerritConnection` will be
936 instantiated for each connection present in the config file
937 and stored here. For instance, `fake_gerrit` will hold the
938 FakeGerritConnection object for a connection named `gerrit`.
939
940 :ivar FakeGearmanServer gearman_server: An instance of
941 :py:class:`~tests.base.FakeGearmanServer` which is the Gearman
942 server that all of the Zuul components in this test use to
943 communicate with each other.
944
945 :ivar RecordingLaunchServer launch_server: An instance of
946 :py:class:`~tests.base.RecordingLaunchServer` which is the
947 Ansible launch server used to run jobs for this test.
948
949 :ivar list builds: A list of :py:class:`~tests.base.FakeBuild` objects
950 representing currently running builds. They are appended to
951 the list in the order they are launched, and removed from this
952 list upon completion.
953
954 :ivar list history: A list of :py:class:`~tests.base.BuildHistory`
955 objects representing completed builds. They are appended to
956 the list in the order they complete.
957
958 """
959
James E. Blair83005782015-12-11 14:46:03 -0800960 config_file = 'zuul.conf'
James E. Blaire1767bc2016-08-02 10:00:27 -0700961 run_ansible = False
James E. Blair3f876d52016-07-22 13:07:14 -0700962
963 def _startMerger(self):
964 self.merge_server = zuul.merger.server.MergeServer(self.config,
965 self.connections)
966 self.merge_server.start()
967
Maru Newby3fe5f852015-01-13 04:22:14 +0000968 def setUp(self):
969 super(ZuulTestCase, self).setUp()
James E. Blair97d902e2014-08-21 13:25:56 -0700970 if USE_TEMPDIR:
971 tmp_root = self.useFixture(fixtures.TempDir(
Joshua Hesketh29d99b72014-08-19 16:27:42 +1000972 rootdir=os.environ.get("ZUUL_TEST_ROOT"))
973 ).path
James E. Blair97d902e2014-08-21 13:25:56 -0700974 else:
975 tmp_root = os.environ.get("ZUUL_TEST_ROOT")
Clark Boylanb640e052014-04-03 16:41:46 -0700976 self.test_root = os.path.join(tmp_root, "zuul-test")
977 self.upstream_root = os.path.join(self.test_root, "upstream")
978 self.git_root = os.path.join(self.test_root, "git")
James E. Blairce8a2132016-05-19 15:21:52 -0700979 self.state_root = os.path.join(self.test_root, "lib")
Clark Boylanb640e052014-04-03 16:41:46 -0700980
981 if os.path.exists(self.test_root):
982 shutil.rmtree(self.test_root)
983 os.makedirs(self.test_root)
984 os.makedirs(self.upstream_root)
James E. Blairce8a2132016-05-19 15:21:52 -0700985 os.makedirs(self.state_root)
Clark Boylanb640e052014-04-03 16:41:46 -0700986
987 # Make per test copy of Configuration.
988 self.setup_config()
James E. Blair59fdbac2015-12-07 17:08:06 -0800989 self.config.set('zuul', 'tenant_config',
Joshua Heskethacccffc2015-03-31 23:38:17 +1100990 os.path.join(FIXTURE_DIR,
James E. Blair59fdbac2015-12-07 17:08:06 -0800991 self.config.get('zuul', 'tenant_config')))
Clark Boylanb640e052014-04-03 16:41:46 -0700992 self.config.set('merger', 'git_dir', self.git_root)
James E. Blairce8a2132016-05-19 15:21:52 -0700993 self.config.set('zuul', 'state_dir', self.state_root)
Clark Boylanb640e052014-04-03 16:41:46 -0700994
995 # For each project in config:
James E. Blair8b1dc3f2016-07-05 16:49:00 -0700996 # TODOv3(jeblair): remove these and replace with new git
997 # filesystem fixtures
Clark Boylanb640e052014-04-03 16:41:46 -0700998 self.init_repo("org/project3")
James E. Blair97d902e2014-08-21 13:25:56 -0700999 self.init_repo("org/project4")
James E. Blairbce35e12014-08-21 14:31:17 -07001000 self.init_repo("org/project5")
1001 self.init_repo("org/project6")
Clark Boylanb640e052014-04-03 16:41:46 -07001002 self.init_repo("org/one-job-project")
1003 self.init_repo("org/nonvoting-project")
1004 self.init_repo("org/templated-project")
1005 self.init_repo("org/layered-project")
1006 self.init_repo("org/node-project")
1007 self.init_repo("org/conflict-project")
1008 self.init_repo("org/noop-project")
1009 self.init_repo("org/experimental-project")
Evgeny Antyshevd6e546c2015-06-11 15:13:57 +00001010 self.init_repo("org/no-jobs-project")
Clark Boylanb640e052014-04-03 16:41:46 -07001011
1012 self.statsd = FakeStatsd()
Ian Wienandff977bf2015-09-30 15:38:47 +10001013 # note, use 127.0.0.1 rather than localhost to avoid getting ipv6
1014 # see: https://github.com/jsocol/pystatsd/issues/61
1015 os.environ['STATSD_HOST'] = '127.0.0.1'
Clark Boylanb640e052014-04-03 16:41:46 -07001016 os.environ['STATSD_PORT'] = str(self.statsd.port)
1017 self.statsd.start()
1018 # the statsd client object is configured in the statsd module import
Monty Taylor74fa3862016-06-02 07:39:49 +03001019 reload_module(statsd)
1020 reload_module(zuul.scheduler)
Clark Boylanb640e052014-04-03 16:41:46 -07001021
1022 self.gearman_server = FakeGearmanServer()
1023
1024 self.config.set('gearman', 'port', str(self.gearman_server.port))
1025
Joshua Hesketh352264b2015-08-11 23:42:08 +10001026 zuul.source.gerrit.GerritSource.replication_timeout = 1.5
1027 zuul.source.gerrit.GerritSource.replication_retry_interval = 0.5
1028 zuul.connection.gerrit.GerritEventConnector.delay = 0.0
Clark Boylanb640e052014-04-03 16:41:46 -07001029
Joshua Hesketh352264b2015-08-11 23:42:08 +10001030 self.sched = zuul.scheduler.Scheduler(self.config)
Clark Boylanb640e052014-04-03 16:41:46 -07001031
1032 self.useFixture(fixtures.MonkeyPatch('swiftclient.client.Connection',
1033 FakeSwiftClientConnection))
1034 self.swift = zuul.lib.swift.Swift(self.config)
1035
Jan Hruban6b71aff2015-10-22 16:58:08 +02001036 self.event_queues = [
1037 self.sched.result_event_queue,
1038 self.sched.trigger_event_queue
1039 ]
1040
James E. Blairfef78942016-03-11 16:28:56 -08001041 self.configure_connections()
Joshua Hesketh352264b2015-08-11 23:42:08 +10001042 self.sched.registerConnections(self.connections)
Joshua Hesketh352264b2015-08-11 23:42:08 +10001043
Clark Boylanb640e052014-04-03 16:41:46 -07001044 def URLOpenerFactory(*args, **kw):
Morgan Fainberg293f7f82016-05-30 14:01:22 -07001045 if isinstance(args[0], urllib.request.Request):
Clark Boylanb640e052014-04-03 16:41:46 -07001046 return old_urlopen(*args, **kw)
Clark Boylanb640e052014-04-03 16:41:46 -07001047 return FakeURLOpener(self.upstream_root, *args, **kw)
1048
Morgan Fainberg293f7f82016-05-30 14:01:22 -07001049 old_urlopen = urllib.request.urlopen
1050 urllib.request.urlopen = URLOpenerFactory
Clark Boylanb640e052014-04-03 16:41:46 -07001051
James E. Blair3f876d52016-07-22 13:07:14 -07001052 self._startMerger()
James E. Blair3f876d52016-07-22 13:07:14 -07001053
James E. Blaire1767bc2016-08-02 10:00:27 -07001054 self.launch_server = RecordingLaunchServer(
1055 self.config, self.connections, _run_ansible=self.run_ansible)
1056 self.launch_server.start()
1057 self.history = self.launch_server.build_history
1058 self.builds = self.launch_server.running_builds
1059
1060 self.launch_client = zuul.launcher.client.LaunchClient(
James E. Blair82938472016-01-11 14:38:13 -08001061 self.config, self.sched, self.swift)
Joshua Hesketh850ccb62014-11-27 11:31:02 +11001062 self.merge_client = zuul.merger.client.MergeClient(
1063 self.config, self.sched)
James E. Blair8d692392016-04-08 17:47:58 -07001064 self.nodepool = zuul.nodepool.Nodepool(self.sched)
Clark Boylanb640e052014-04-03 16:41:46 -07001065
James E. Blaire1767bc2016-08-02 10:00:27 -07001066 self.sched.setLauncher(self.launch_client)
Clark Boylanb640e052014-04-03 16:41:46 -07001067 self.sched.setMerger(self.merge_client)
James E. Blair8d692392016-04-08 17:47:58 -07001068 self.sched.setNodepool(self.nodepool)
Clark Boylanb640e052014-04-03 16:41:46 -07001069
Paul Belanger88ef0ea2015-12-23 11:57:02 -05001070 self.webapp = zuul.webapp.WebApp(
1071 self.sched, port=0, listen_address='127.0.0.1')
Joshua Hesketh850ccb62014-11-27 11:31:02 +11001072 self.rpc = zuul.rpclistener.RPCListener(self.config, self.sched)
Clark Boylanb640e052014-04-03 16:41:46 -07001073
1074 self.sched.start()
1075 self.sched.reconfigure(self.config)
1076 self.sched.resume()
1077 self.webapp.start()
1078 self.rpc.start()
James E. Blaire1767bc2016-08-02 10:00:27 -07001079 self.launch_client.gearman.waitForServer()
Clark Boylanb640e052014-04-03 16:41:46 -07001080
1081 self.addCleanup(self.assertFinalState)
1082 self.addCleanup(self.shutdown)
1083
James E. Blairfef78942016-03-11 16:28:56 -08001084 def configure_connections(self):
Joshua Hesketh352264b2015-08-11 23:42:08 +10001085 # Register connections from the config
1086 self.smtp_messages = []
Joshua Hesketh850ccb62014-11-27 11:31:02 +11001087
Joshua Hesketh352264b2015-08-11 23:42:08 +10001088 def FakeSMTPFactory(*args, **kw):
1089 args = [self.smtp_messages] + list(args)
1090 return FakeSMTP(*args, **kw)
Joshua Hesketh850ccb62014-11-27 11:31:02 +11001091
Joshua Hesketh352264b2015-08-11 23:42:08 +10001092 self.useFixture(fixtures.MonkeyPatch('smtplib.SMTP', FakeSMTPFactory))
Joshua Hesketh850ccb62014-11-27 11:31:02 +11001093
Joshua Hesketh352264b2015-08-11 23:42:08 +10001094 # Set a changes database so multiple FakeGerrit's can report back to
1095 # a virtual canonical database given by the configured hostname
1096 self.gerrit_changes_dbs = {}
1097 self.gerrit_queues_dbs = {}
James E. Blairfef78942016-03-11 16:28:56 -08001098 self.connections = zuul.lib.connections.ConnectionRegistry()
Joshua Hesketh850ccb62014-11-27 11:31:02 +11001099
Joshua Hesketh352264b2015-08-11 23:42:08 +10001100 for section_name in self.config.sections():
1101 con_match = re.match(r'^connection ([\'\"]?)(.*)(\1)$',
1102 section_name, re.I)
1103 if not con_match:
1104 continue
1105 con_name = con_match.group(2)
1106 con_config = dict(self.config.items(section_name))
1107
1108 if 'driver' not in con_config:
1109 raise Exception("No driver specified for connection %s."
1110 % con_name)
1111
1112 con_driver = con_config['driver']
1113
1114 # TODO(jhesketh): load the required class automatically
1115 if con_driver == 'gerrit':
Joshua Heskethacccffc2015-03-31 23:38:17 +11001116 if con_config['server'] not in self.gerrit_changes_dbs.keys():
1117 self.gerrit_changes_dbs[con_config['server']] = {}
1118 if con_config['server'] not in self.gerrit_queues_dbs.keys():
1119 self.gerrit_queues_dbs[con_config['server']] = \
1120 Queue.Queue()
1121 self.event_queues.append(
1122 self.gerrit_queues_dbs[con_config['server']])
James E. Blair83005782015-12-11 14:46:03 -08001123 self.connections.connections[con_name] = FakeGerritConnection(
Joshua Hesketh352264b2015-08-11 23:42:08 +10001124 con_name, con_config,
Joshua Heskethacccffc2015-03-31 23:38:17 +11001125 changes_db=self.gerrit_changes_dbs[con_config['server']],
1126 queues_db=self.gerrit_queues_dbs[con_config['server']],
Jan Hruban6b71aff2015-10-22 16:58:08 +02001127 upstream_root=self.upstream_root
Joshua Hesketh352264b2015-08-11 23:42:08 +10001128 )
James E. Blair83005782015-12-11 14:46:03 -08001129 setattr(self, 'fake_' + con_name,
1130 self.connections.connections[con_name])
Joshua Hesketh352264b2015-08-11 23:42:08 +10001131 elif con_driver == 'smtp':
James E. Blair83005782015-12-11 14:46:03 -08001132 self.connections.connections[con_name] = \
Joshua Hesketh352264b2015-08-11 23:42:08 +10001133 zuul.connection.smtp.SMTPConnection(con_name, con_config)
1134 else:
1135 raise Exception("Unknown driver, %s, for connection %s"
1136 % (con_config['driver'], con_name))
1137
1138 # If the [gerrit] or [smtp] sections still exist, load them in as a
1139 # connection named 'gerrit' or 'smtp' respectfully
1140
1141 if 'gerrit' in self.config.sections():
1142 self.gerrit_changes_dbs['gerrit'] = {}
1143 self.gerrit_queues_dbs['gerrit'] = Queue.Queue()
Jan Hruban6b71aff2015-10-22 16:58:08 +02001144 self.event_queues.append(self.gerrit_queues_dbs['gerrit'])
James E. Blair83005782015-12-11 14:46:03 -08001145 self.connections.connections['gerrit'] = FakeGerritConnection(
Joshua Hesketh352264b2015-08-11 23:42:08 +10001146 '_legacy_gerrit', dict(self.config.items('gerrit')),
1147 changes_db=self.gerrit_changes_dbs['gerrit'],
1148 queues_db=self.gerrit_queues_dbs['gerrit'])
1149
1150 if 'smtp' in self.config.sections():
James E. Blair83005782015-12-11 14:46:03 -08001151 self.connections.connections['smtp'] = \
Joshua Hesketh352264b2015-08-11 23:42:08 +10001152 zuul.connection.smtp.SMTPConnection(
1153 '_legacy_smtp', dict(self.config.items('smtp')))
Joshua Hesketh850ccb62014-11-27 11:31:02 +11001154
James E. Blair83005782015-12-11 14:46:03 -08001155 def setup_config(self):
James E. Blaire7b99a02016-08-05 14:27:34 -07001156 # This creates the per-test configuration object. It can be
1157 # overriden by subclasses, but should not need to be since it
1158 # obeys the config_file and tenant_config_file attributes.
Clark Boylanb640e052014-04-03 16:41:46 -07001159 self.config = ConfigParser.ConfigParser()
James E. Blair83005782015-12-11 14:46:03 -08001160 self.config.read(os.path.join(FIXTURE_DIR, self.config_file))
James E. Blair2a629ec2015-12-22 15:32:02 -08001161 if hasattr(self, 'tenant_config_file'):
1162 self.config.set('zuul', 'tenant_config', self.tenant_config_file)
James E. Blair96c6bf82016-01-15 16:20:40 -08001163 git_path = os.path.join(
1164 os.path.dirname(
1165 os.path.join(FIXTURE_DIR, self.tenant_config_file)),
1166 'git')
1167 if os.path.exists(git_path):
1168 for reponame in os.listdir(git_path):
James E. Blair8b1dc3f2016-07-05 16:49:00 -07001169 project = reponame.replace('_', '/')
1170 self.copyDirToRepo(project,
James E. Blair96c6bf82016-01-15 16:20:40 -08001171 os.path.join(git_path, reponame))
1172
1173 def copyDirToRepo(self, project, source_path):
James E. Blair8b1dc3f2016-07-05 16:49:00 -07001174 self.init_repo(project)
James E. Blair96c6bf82016-01-15 16:20:40 -08001175
1176 files = {}
1177 for (dirpath, dirnames, filenames) in os.walk(source_path):
1178 for filename in filenames:
1179 test_tree_filepath = os.path.join(dirpath, filename)
1180 common_path = os.path.commonprefix([test_tree_filepath,
1181 source_path])
1182 relative_filepath = test_tree_filepath[len(common_path) + 1:]
1183 with open(test_tree_filepath, 'r') as f:
1184 content = f.read()
1185 files[relative_filepath] = content
1186 self.addCommitToRepo(project, 'add content from fixture',
James E. Blair8b1dc3f2016-07-05 16:49:00 -07001187 files, branch='master', tag='init')
James E. Blair83005782015-12-11 14:46:03 -08001188
Clark Boylanb640e052014-04-03 16:41:46 -07001189 def assertFinalState(self):
Clark Boylanb640e052014-04-03 16:41:46 -07001190 # Make sure that git.Repo objects have been garbage collected.
1191 repos = []
1192 gc.collect()
1193 for obj in gc.get_objects():
1194 if isinstance(obj, git.Repo):
1195 repos.append(obj)
1196 self.assertEqual(len(repos), 0)
1197 self.assertEmptyQueues()
James E. Blair83005782015-12-11 14:46:03 -08001198 ipm = zuul.manager.independent.IndependentPipelineManager
James E. Blair59fdbac2015-12-07 17:08:06 -08001199 for tenant in self.sched.abide.tenants.values():
1200 for pipeline in tenant.layout.pipelines.values():
James E. Blair83005782015-12-11 14:46:03 -08001201 if isinstance(pipeline.manager, ipm):
James E. Blair59fdbac2015-12-07 17:08:06 -08001202 self.assertEqual(len(pipeline.queues), 0)
Clark Boylanb640e052014-04-03 16:41:46 -07001203
1204 def shutdown(self):
1205 self.log.debug("Shutting down after tests")
James E. Blaire1767bc2016-08-02 10:00:27 -07001206 self.launch_client.stop()
James E. Blair3f876d52016-07-22 13:07:14 -07001207 self.merge_server.stop()
1208 self.merge_server.join()
Clark Boylanb640e052014-04-03 16:41:46 -07001209 self.merge_client.stop()
James E. Blaire1767bc2016-08-02 10:00:27 -07001210 self.launch_server.stop()
Clark Boylanb640e052014-04-03 16:41:46 -07001211 self.sched.stop()
1212 self.sched.join()
1213 self.statsd.stop()
1214 self.statsd.join()
1215 self.webapp.stop()
1216 self.webapp.join()
1217 self.rpc.stop()
1218 self.rpc.join()
1219 self.gearman_server.shutdown()
1220 threads = threading.enumerate()
1221 if len(threads) > 1:
1222 self.log.error("More than one thread is running: %s" % threads)
Clark Boylanb640e052014-04-03 16:41:46 -07001223
1224 def init_repo(self, project):
1225 parts = project.split('/')
1226 path = os.path.join(self.upstream_root, *parts[:-1])
1227 if not os.path.exists(path):
1228 os.makedirs(path)
1229 path = os.path.join(self.upstream_root, project)
1230 repo = git.Repo.init(path)
1231
Morgan Fainberg78c301a2016-07-14 13:47:01 -07001232 with repo.config_writer() as config_writer:
1233 config_writer.set_value('user', 'email', 'user@example.com')
1234 config_writer.set_value('user', 'name', 'User Name')
Clark Boylanb640e052014-04-03 16:41:46 -07001235
Clark Boylanb640e052014-04-03 16:41:46 -07001236 repo.index.commit('initial commit')
1237 master = repo.create_head('master')
Clark Boylanb640e052014-04-03 16:41:46 -07001238
James E. Blair97d902e2014-08-21 13:25:56 -07001239 repo.head.reference = master
James E. Blair879dafb2015-07-17 14:04:49 -07001240 zuul.merger.merger.reset_repo_to_head(repo)
James E. Blair97d902e2014-08-21 13:25:56 -07001241 repo.git.clean('-x', '-f', '-d')
1242
James E. Blair97d902e2014-08-21 13:25:56 -07001243 def create_branch(self, project, branch):
1244 path = os.path.join(self.upstream_root, project)
1245 repo = git.Repo.init(path)
1246 fn = os.path.join(path, 'README')
1247
1248 branch_head = repo.create_head(branch)
1249 repo.head.reference = branch_head
Clark Boylanb640e052014-04-03 16:41:46 -07001250 f = open(fn, 'a')
James E. Blair97d902e2014-08-21 13:25:56 -07001251 f.write("test %s\n" % branch)
Clark Boylanb640e052014-04-03 16:41:46 -07001252 f.close()
1253 repo.index.add([fn])
James E. Blair97d902e2014-08-21 13:25:56 -07001254 repo.index.commit('%s commit' % branch)
Clark Boylanb640e052014-04-03 16:41:46 -07001255
James E. Blair97d902e2014-08-21 13:25:56 -07001256 repo.head.reference = repo.heads['master']
James E. Blair879dafb2015-07-17 14:04:49 -07001257 zuul.merger.merger.reset_repo_to_head(repo)
Clark Boylanb640e052014-04-03 16:41:46 -07001258 repo.git.clean('-x', '-f', '-d')
1259
Sachi King9f16d522016-03-16 12:20:45 +11001260 def create_commit(self, project):
1261 path = os.path.join(self.upstream_root, project)
1262 repo = git.Repo(path)
1263 repo.head.reference = repo.heads['master']
1264 file_name = os.path.join(path, 'README')
1265 with open(file_name, 'a') as f:
1266 f.write('creating fake commit\n')
1267 repo.index.add([file_name])
1268 commit = repo.index.commit('Creating a fake commit')
1269 return commit.hexsha
1270
Clark Boylanb640e052014-04-03 16:41:46 -07001271 def ref_has_change(self, ref, change):
1272 path = os.path.join(self.git_root, change.project)
1273 repo = git.Repo(path)
Mike Heald8225f522014-11-21 09:52:33 +00001274 try:
1275 for commit in repo.iter_commits(ref):
1276 if commit.message.strip() == ('%s-1' % change.subject):
1277 return True
1278 except GitCommandError:
1279 pass
Clark Boylanb640e052014-04-03 16:41:46 -07001280 return False
1281
James E. Blairb8c16472015-05-05 14:55:26 -07001282 def orderedRelease(self):
1283 # Run one build at a time to ensure non-race order:
1284 while len(self.builds):
1285 self.release(self.builds[0])
1286 self.waitUntilSettled()
1287
Clark Boylanb640e052014-04-03 16:41:46 -07001288 def release(self, job):
1289 if isinstance(job, FakeBuild):
1290 job.release()
1291 else:
1292 job.waiting = False
1293 self.log.debug("Queued job %s released" % job.unique)
1294 self.gearman_server.wakeConnections()
1295
1296 def getParameter(self, job, name):
1297 if isinstance(job, FakeBuild):
1298 return job.parameters[name]
1299 else:
1300 parameters = json.loads(job.arguments)
1301 return parameters[name]
1302
1303 def resetGearmanServer(self):
James E. Blaire1767bc2016-08-02 10:00:27 -07001304 self.launch_server.worker.setFunctions([])
Clark Boylanb640e052014-04-03 16:41:46 -07001305 while True:
1306 done = True
1307 for connection in self.gearman_server.active_connections:
1308 if (connection.functions and
1309 connection.client_id not in ['Zuul RPC Listener',
1310 'Zuul Merger']):
1311 done = False
1312 if done:
1313 break
1314 time.sleep(0)
1315 self.gearman_server.functions = set()
1316 self.rpc.register()
Clark Boylanb640e052014-04-03 16:41:46 -07001317
1318 def haveAllBuildsReported(self):
1319 # See if Zuul is waiting on a meta job to complete
James E. Blaire1767bc2016-08-02 10:00:27 -07001320 if self.launch_client.meta_jobs:
Clark Boylanb640e052014-04-03 16:41:46 -07001321 return False
1322 # Find out if every build that the worker has completed has been
1323 # reported back to Zuul. If it hasn't then that means a Gearman
1324 # event is still in transit and the system is not stable.
James E. Blair3f876d52016-07-22 13:07:14 -07001325 for build in self.history:
James E. Blaire1767bc2016-08-02 10:00:27 -07001326 zbuild = self.launch_client.builds.get(build.uuid)
Clark Boylanb640e052014-04-03 16:41:46 -07001327 if not zbuild:
1328 # It has already been reported
1329 continue
1330 # It hasn't been reported yet.
1331 return False
1332 # Make sure that none of the worker connections are in GRAB_WAIT
James E. Blaire1767bc2016-08-02 10:00:27 -07001333 for connection in self.launch_server.worker.active_connections:
Clark Boylanb640e052014-04-03 16:41:46 -07001334 if connection.state == 'GRAB_WAIT':
1335 return False
1336 return True
1337
1338 def areAllBuildsWaiting(self):
James E. Blaire1767bc2016-08-02 10:00:27 -07001339 builds = self.launch_client.builds.values()
Clark Boylanb640e052014-04-03 16:41:46 -07001340 for build in builds:
1341 client_job = None
James E. Blaire1767bc2016-08-02 10:00:27 -07001342 for conn in self.launch_client.gearman.active_connections:
Clark Boylanb640e052014-04-03 16:41:46 -07001343 for j in conn.related_jobs.values():
1344 if j.unique == build.uuid:
1345 client_job = j
1346 break
1347 if not client_job:
1348 self.log.debug("%s is not known to the gearman client" %
1349 build)
James E. Blairf15139b2015-04-02 16:37:15 -07001350 return False
Clark Boylanb640e052014-04-03 16:41:46 -07001351 if not client_job.handle:
1352 self.log.debug("%s has no handle" % client_job)
James E. Blairf15139b2015-04-02 16:37:15 -07001353 return False
Clark Boylanb640e052014-04-03 16:41:46 -07001354 server_job = self.gearman_server.jobs.get(client_job.handle)
1355 if not server_job:
1356 self.log.debug("%s is not known to the gearman server" %
1357 client_job)
James E. Blairf15139b2015-04-02 16:37:15 -07001358 return False
Clark Boylanb640e052014-04-03 16:41:46 -07001359 if not hasattr(server_job, 'waiting'):
1360 self.log.debug("%s is being enqueued" % server_job)
James E. Blairf15139b2015-04-02 16:37:15 -07001361 return False
Clark Boylanb640e052014-04-03 16:41:46 -07001362 if server_job.waiting:
1363 continue
James E. Blairbbda4702016-03-09 15:19:56 -08001364 if build.number is None:
1365 self.log.debug("%s has not reported start" % build)
1366 return False
James E. Blairab7132b2016-08-05 12:36:22 -07001367 worker_build = self.launch_server.job_builds.get(server_job.unique)
James E. Blair962220f2016-08-03 11:22:38 -07001368 if worker_build:
1369 if worker_build.isWaiting():
1370 continue
1371 else:
1372 self.log.debug("%s is running" % worker_build)
1373 return False
Clark Boylanb640e052014-04-03 16:41:46 -07001374 else:
James E. Blair962220f2016-08-03 11:22:38 -07001375 self.log.debug("%s is unassigned" % server_job)
James E. Blairf15139b2015-04-02 16:37:15 -07001376 return False
1377 return True
Clark Boylanb640e052014-04-03 16:41:46 -07001378
Jan Hruban6b71aff2015-10-22 16:58:08 +02001379 def eventQueuesEmpty(self):
1380 for queue in self.event_queues:
1381 yield queue.empty()
1382
1383 def eventQueuesJoin(self):
1384 for queue in self.event_queues:
1385 queue.join()
1386
Clark Boylanb640e052014-04-03 16:41:46 -07001387 def waitUntilSettled(self):
1388 self.log.debug("Waiting until settled...")
1389 start = time.time()
1390 while True:
1391 if time.time() - start > 10:
James E. Blair622c9682016-06-09 08:14:53 -07001392 self.log.debug("Queue status:")
1393 for queue in self.event_queues:
1394 self.log.debug(" %s: %s" % (queue, queue.empty()))
1395 self.log.debug("All builds waiting: %s" %
1396 (self.areAllBuildsWaiting(),))
Clark Boylanb640e052014-04-03 16:41:46 -07001397 raise Exception("Timeout waiting for Zuul to settle")
1398 # Make sure no new events show up while we're checking
James E. Blair3f876d52016-07-22 13:07:14 -07001399
James E. Blaire1767bc2016-08-02 10:00:27 -07001400 self.launch_server.lock.acquire()
Clark Boylanb640e052014-04-03 16:41:46 -07001401 # have all build states propogated to zuul?
1402 if self.haveAllBuildsReported():
1403 # Join ensures that the queue is empty _and_ events have been
1404 # processed
Jan Hruban6b71aff2015-10-22 16:58:08 +02001405 self.eventQueuesJoin()
Clark Boylanb640e052014-04-03 16:41:46 -07001406 self.sched.run_handler_lock.acquire()
James E. Blair14abdf42015-12-09 16:11:53 -08001407 if (not self.merge_client.jobs and
Jan Hruban6b71aff2015-10-22 16:58:08 +02001408 all(self.eventQueuesEmpty()) and
Clark Boylanb640e052014-04-03 16:41:46 -07001409 self.haveAllBuildsReported() and
1410 self.areAllBuildsWaiting()):
1411 self.sched.run_handler_lock.release()
James E. Blaire1767bc2016-08-02 10:00:27 -07001412 self.launch_server.lock.release()
Clark Boylanb640e052014-04-03 16:41:46 -07001413 self.log.debug("...settled.")
1414 return
1415 self.sched.run_handler_lock.release()
James E. Blaire1767bc2016-08-02 10:00:27 -07001416 self.launch_server.lock.release()
Clark Boylanb640e052014-04-03 16:41:46 -07001417 self.sched.wake_event.wait(0.1)
1418
1419 def countJobResults(self, jobs, result):
1420 jobs = filter(lambda x: x.result == result, jobs)
1421 return len(jobs)
1422
James E. Blair96c6bf82016-01-15 16:20:40 -08001423 def getJobFromHistory(self, name, project=None):
James E. Blair3f876d52016-07-22 13:07:14 -07001424 for job in self.history:
1425 if (job.name == name and
1426 (project is None or
1427 job.parameters['ZUUL_PROJECT'] == project)):
1428 return job
Clark Boylanb640e052014-04-03 16:41:46 -07001429 raise Exception("Unable to find job %s in history" % name)
1430
1431 def assertEmptyQueues(self):
1432 # Make sure there are no orphaned jobs
James E. Blair59fdbac2015-12-07 17:08:06 -08001433 for tenant in self.sched.abide.tenants.values():
1434 for pipeline in tenant.layout.pipelines.values():
1435 for queue in pipeline.queues:
1436 if len(queue.queue) != 0:
Joshua Hesketh0aa7e8b2016-07-14 00:12:25 +10001437 print('pipeline %s queue %s contents %s' % (
1438 pipeline.name, queue.name, queue.queue))
James E. Blair59fdbac2015-12-07 17:08:06 -08001439 self.assertEqual(len(queue.queue), 0,
1440 "Pipelines queues should be empty")
Clark Boylanb640e052014-04-03 16:41:46 -07001441
1442 def assertReportedStat(self, key, value=None, kind=None):
1443 start = time.time()
1444 while time.time() < (start + 5):
1445 for stat in self.statsd.stats:
1446 pprint.pprint(self.statsd.stats)
1447 k, v = stat.split(':')
1448 if key == k:
1449 if value is None and kind is None:
1450 return
1451 elif value:
1452 if value == v:
1453 return
1454 elif kind:
1455 if v.endswith('|' + kind):
1456 return
1457 time.sleep(0.1)
1458
1459 pprint.pprint(self.statsd.stats)
1460 raise Exception("Key %s not found in reported stats" % key)
James E. Blair59fdbac2015-12-07 17:08:06 -08001461
1462 def getPipeline(self, name):
James E. Blairf84026c2015-12-08 16:11:46 -08001463 return self.sched.abide.tenants.values()[0].layout.pipelines.get(name)
1464
1465 def updateConfigLayout(self, path):
1466 root = os.path.join(self.test_root, "config")
1467 os.makedirs(root)
1468 f = tempfile.NamedTemporaryFile(dir=root, delete=False)
1469 f.write("""
1470tenants:
1471 - name: openstack
1472 include:
1473 - %s
1474 """ % os.path.abspath(path))
1475 f.close()
1476 self.config.set('zuul', 'tenant_config', f.name)
James E. Blair14abdf42015-12-09 16:11:53 -08001477
James E. Blair8b1dc3f2016-07-05 16:49:00 -07001478 def addCommitToRepo(self, project, message, files,
1479 branch='master', tag=None):
James E. Blair14abdf42015-12-09 16:11:53 -08001480 path = os.path.join(self.upstream_root, project)
1481 repo = git.Repo(path)
1482 repo.head.reference = branch
1483 zuul.merger.merger.reset_repo_to_head(repo)
1484 for fn, content in files.items():
1485 fn = os.path.join(path, fn)
1486 with open(fn, 'w') as f:
1487 f.write(content)
1488 repo.index.add([fn])
1489 commit = repo.index.commit(message)
1490 repo.heads[branch].commit = commit
1491 repo.head.reference = branch
1492 repo.git.clean('-x', '-f', '-d')
1493 repo.heads[branch].checkout()
James E. Blair8b1dc3f2016-07-05 16:49:00 -07001494 if tag:
1495 repo.create_tag(tag)
James E. Blair3f876d52016-07-22 13:07:14 -07001496
1497
1498class AnsibleZuulTestCase(ZuulTestCase):
1499 """ZuulTestCase but with an actual ansible launcher running"""
James E. Blaire1767bc2016-08-02 10:00:27 -07001500 run_ansible = True