blob: 770c6360911a0809fac883fcd73fe403e8b24e97 [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
Monty Taylorb934c1a2017-06-16 19:31:47 -050018import configparser
Adam Gandelmand81dd762017-02-09 15:15:49 -080019import datetime
Clark Boylanb640e052014-04-03 16:41:46 -070020import gc
21import hashlib
Monty Taylorb934c1a2017-06-16 19:31:47 -050022import importlib
23from io import StringIO
Clark Boylanb640e052014-04-03 16:41:46 -070024import json
25import logging
26import os
Monty Taylorb934c1a2017-06-16 19:31:47 -050027import queue
Clark Boylanb640e052014-04-03 16:41:46 -070028import random
29import re
30import select
31import shutil
32import socket
33import string
34import subprocess
James E. Blair1c236df2017-02-01 14:07:24 -080035import sys
James E. Blairf84026c2015-12-08 16:11:46 -080036import tempfile
Clark Boylanb640e052014-04-03 16:41:46 -070037import threading
Clark Boylan8208c192017-04-24 18:08:08 -070038import traceback
Clark Boylanb640e052014-04-03 16:41:46 -070039import time
Joshua Heskethd78b4482015-09-14 16:56:34 -060040import uuid
Monty Taylorb934c1a2017-06-16 19:31:47 -050041import urllib
Joshua Heskethd78b4482015-09-14 16:56:34 -060042
Clark Boylanb640e052014-04-03 16:41:46 -070043
44import git
45import gear
46import fixtures
James E. Blair498059b2016-12-20 13:50:13 -080047import kazoo.client
James E. Blairdce6cea2016-12-20 16:45:32 -080048import kazoo.exceptions
Joshua Heskethd78b4482015-09-14 16:56:34 -060049import pymysql
Clark Boylanb640e052014-04-03 16:41:46 -070050import statsd
51import testtools
James E. Blair1c236df2017-02-01 14:07:24 -080052import testtools.content
53import testtools.content_type
Clint Byrum3343e3e2016-11-15 16:05:03 -080054from git.exc import NoSuchPathError
Ricardo Carrillo Cruz22994f92016-12-02 11:41:58 +000055import yaml
Clark Boylanb640e052014-04-03 16:41:46 -070056
James E. Blaire511d2f2016-12-08 15:22:26 -080057import zuul.driver.gerrit.gerritsource as gerritsource
58import zuul.driver.gerrit.gerritconnection as gerritconnection
Gregory Haynes4fc12542015-04-22 20:38:06 -070059import zuul.driver.github.githubconnection as githubconnection
Clark Boylanb640e052014-04-03 16:41:46 -070060import zuul.scheduler
61import zuul.webapp
62import zuul.rpclistener
Paul Belanger174a8272017-03-14 13:20:10 -040063import zuul.executor.server
64import zuul.executor.client
James E. Blair83005782015-12-11 14:46:03 -080065import zuul.lib.connections
Clark Boylanb640e052014-04-03 16:41:46 -070066import zuul.merger.client
James E. Blair879dafb2015-07-17 14:04:49 -070067import zuul.merger.merger
68import zuul.merger.server
Tobias Henkeld91b4d72017-05-23 15:43:40 +020069import zuul.model
James E. Blair8d692392016-04-08 17:47:58 -070070import zuul.nodepool
James E. Blairdce6cea2016-12-20 16:45:32 -080071import zuul.zk
Jan Hruban49bff072015-11-03 11:45:46 +010072from zuul.exceptions import MergeFailure
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():
Clint Byrumc0923d52017-05-10 15:47:41 -040092 return hashlib.sha1(str(random.random()).encode('ascii')).hexdigest()
Clark Boylanb640e052014-04-03 16:41:46 -070093
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):
Tobias Henkelea98a192017-05-29 21:15:17 +0200136 categories = {'Approved': ('Approved', -1, 1),
137 'Code-Review': ('Code-Review', -2, 2),
138 'Verified': ('Verified', -2, 2)}
139
Clark Boylanb640e052014-04-03 16:41:46 -0700140 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"},
Tobias Henkelbf24fd12017-07-27 06:13:07 +0200294 "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 = {
Tobias Henkelbf24fd12017-07-27 06:13:07 +0200334 'description': self.categories[category][0],
335 'type': category,
Joshua Hesketh29d99b72014-08-19 16:27:42 +1000336 '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'][:]):
Tobias Henkelbf24fd12017-07-27 06:13:07 +0200344 if x['by']['username'] == username and x['type'] == category:
Clark Boylanb640e052014-04-03 16:41:46 -0700345 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'],
James E. Blairdb93b302017-07-19 15:33:11 -0700413 'ref': self.patchsets[-1]['ref'],
414 'revision': self.patchsets[-1]['revision']
Clark Boylanb640e052014-04-03 16:41:46 -0700415 }
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
Monty Taylorb934c1a2017-06-16 19:31:47 -0500463 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
Clark Boylanb640e052014-04-03 16:41:46 -0700498 change.messages.append(message)
Joshua Hesketh642824b2014-07-01 17:54:59 +1000499
Clark Boylanb640e052014-04-03 16:41:46 -0700500 if 'submit' in action:
501 change.setMerged()
502 if message:
503 change.setReported()
504
505 def query(self, number):
506 change = self.changes.get(int(number))
507 if change:
508 return change.query()
509 return {}
510
James E. Blairc494d542014-08-06 09:23:52 -0700511 def simpleQuery(self, query):
James E. Blair96698e22015-04-02 07:48:21 -0700512 self.log.debug("simpleQuery: %s" % query)
James E. Blairf8ff9932014-08-15 15:24:24 -0700513 self.queries.append(query)
James E. Blair5ee24252014-12-30 10:12:29 -0800514 if query.startswith('change:'):
515 # Query a specific changeid
516 changeid = query[len('change:'):]
517 l = [change.query() for change in self.changes.values()
518 if change.data['id'] == changeid]
James E. Blair96698e22015-04-02 07:48:21 -0700519 elif query.startswith('message:'):
520 # Query the content of a commit message
521 msg = query[len('message:'):].strip()
522 l = [change.query() for change in self.changes.values()
523 if msg in change.data['commitMessage']]
James E. Blair5ee24252014-12-30 10:12:29 -0800524 else:
525 # Query all open changes
526 l = [change.query() for change in self.changes.values()]
James E. Blairf8ff9932014-08-15 15:24:24 -0700527 return l
James E. Blairc494d542014-08-06 09:23:52 -0700528
Joshua Hesketh352264b2015-08-11 23:42:08 +1000529 def _start_watcher_thread(self, *args, **kw):
Clark Boylanb640e052014-04-03 16:41:46 -0700530 pass
531
Tobias Henkeld91b4d72017-05-23 15:43:40 +0200532 def _uploadPack(self, project):
533 ret = ('00a31270149696713ba7e06f1beb760f20d359c4abed HEAD\x00'
534 'multi_ack thin-pack side-band side-band-64k ofs-delta '
535 'shallow no-progress include-tag multi_ack_detailed no-done\n')
536 path = os.path.join(self.upstream_root, project.name)
537 repo = git.Repo(path)
538 for ref in repo.refs:
539 r = ref.object.hexsha + ' ' + ref.path + '\n'
540 ret += '%04x%s' % (len(r) + 4, r)
541 ret += '0000'
542 return ret
543
Joshua Hesketh352264b2015-08-11 23:42:08 +1000544 def getGitUrl(self, project):
545 return os.path.join(self.upstream_root, project.name)
546
Clark Boylanb640e052014-04-03 16:41:46 -0700547
Gregory Haynes4fc12542015-04-22 20:38:06 -0700548class GithubChangeReference(git.Reference):
549 _common_path_default = "refs/pull"
550 _points_to_commits_only = True
551
552
553class FakeGithubPullRequest(object):
554
555 def __init__(self, github, number, project, branch,
Jesse Keatingae4cd272017-01-30 17:10:44 -0800556 subject, upstream_root, files=[], number_of_commits=1,
Jesse Keating152a4022017-07-07 08:39:52 -0700557 writers=[], body=None):
Gregory Haynes4fc12542015-04-22 20:38:06 -0700558 """Creates a new PR with several commits.
559 Sends an event about opened PR."""
560 self.github = github
561 self.source = github
562 self.number = number
563 self.project = project
564 self.branch = branch
Jan Hruban37615e52015-11-19 14:30:49 +0100565 self.subject = subject
Jesse Keatinga41566f2017-06-14 18:17:51 -0700566 self.body = body
Jan Hruban37615e52015-11-19 14:30:49 +0100567 self.number_of_commits = 0
Gregory Haynes4fc12542015-04-22 20:38:06 -0700568 self.upstream_root = upstream_root
Jan Hruban570d01c2016-03-10 21:51:32 +0100569 self.files = []
Gregory Haynes4fc12542015-04-22 20:38:06 -0700570 self.comments = []
Jan Hruban16ad31f2015-11-07 14:39:07 +0100571 self.labels = []
Jan Hrubane252a732017-01-03 15:03:09 +0100572 self.statuses = {}
Jesse Keatingae4cd272017-01-30 17:10:44 -0800573 self.reviews = []
574 self.writers = []
Gregory Haynes4fc12542015-04-22 20:38:06 -0700575 self.updated_at = None
576 self.head_sha = None
Jan Hruban49bff072015-11-03 11:45:46 +0100577 self.is_merged = False
Jan Hruban3b415922016-02-03 13:10:22 +0100578 self.merge_message = None
Jesse Keating4a27f132017-05-25 16:44:01 -0700579 self.state = 'open'
Gregory Haynes4fc12542015-04-22 20:38:06 -0700580 self._createPRRef()
Jan Hruban570d01c2016-03-10 21:51:32 +0100581 self._addCommitToRepo(files=files)
Gregory Haynes4fc12542015-04-22 20:38:06 -0700582 self._updateTimeStamp()
583
Jan Hruban570d01c2016-03-10 21:51:32 +0100584 def addCommit(self, files=[]):
Gregory Haynes4fc12542015-04-22 20:38:06 -0700585 """Adds a commit on top of the actual PR head."""
Jan Hruban570d01c2016-03-10 21:51:32 +0100586 self._addCommitToRepo(files=files)
Gregory Haynes4fc12542015-04-22 20:38:06 -0700587 self._updateTimeStamp()
588
Jan Hruban570d01c2016-03-10 21:51:32 +0100589 def forcePush(self, files=[]):
Gregory Haynes4fc12542015-04-22 20:38:06 -0700590 """Clears actual commits and add a commit on top of the base."""
Jan Hruban570d01c2016-03-10 21:51:32 +0100591 self._addCommitToRepo(files=files, reset=True)
Gregory Haynes4fc12542015-04-22 20:38:06 -0700592 self._updateTimeStamp()
593
594 def getPullRequestOpenedEvent(self):
595 return self._getPullRequestEvent('opened')
596
597 def getPullRequestSynchronizeEvent(self):
598 return self._getPullRequestEvent('synchronize')
599
600 def getPullRequestReopenedEvent(self):
601 return self._getPullRequestEvent('reopened')
602
603 def getPullRequestClosedEvent(self):
604 return self._getPullRequestEvent('closed')
605
Jesse Keatinga41566f2017-06-14 18:17:51 -0700606 def getPullRequestEditedEvent(self):
607 return self._getPullRequestEvent('edited')
608
Gregory Haynes4fc12542015-04-22 20:38:06 -0700609 def addComment(self, message):
610 self.comments.append(message)
611 self._updateTimeStamp()
612
Jan Hrubanc7ab1602015-10-14 15:29:33 +0200613 def getCommentAddedEvent(self, text):
614 name = 'issue_comment'
615 data = {
616 'action': 'created',
617 'issue': {
618 'number': self.number
619 },
620 'comment': {
621 'body': text
622 },
623 'repository': {
624 'full_name': self.project
Jan Hruban3b415922016-02-03 13:10:22 +0100625 },
626 'sender': {
627 'login': 'ghuser'
Jan Hrubanc7ab1602015-10-14 15:29:33 +0200628 }
629 }
630 return (name, data)
631
Jesse Keating5c05a9f2017-01-12 14:44:58 -0800632 def getReviewAddedEvent(self, review):
633 name = 'pull_request_review'
634 data = {
635 'action': 'submitted',
636 'pull_request': {
637 'number': self.number,
638 'title': self.subject,
639 'updated_at': self.updated_at,
640 'base': {
641 'ref': self.branch,
642 'repo': {
643 'full_name': self.project
644 }
645 },
646 'head': {
647 'sha': self.head_sha
648 }
649 },
650 'review': {
651 'state': review
652 },
653 'repository': {
654 'full_name': self.project
655 },
656 'sender': {
657 'login': 'ghuser'
658 }
659 }
660 return (name, data)
661
Jan Hruban16ad31f2015-11-07 14:39:07 +0100662 def addLabel(self, name):
663 if name not in self.labels:
664 self.labels.append(name)
665 self._updateTimeStamp()
666 return self._getLabelEvent(name)
667
668 def removeLabel(self, name):
669 if name in self.labels:
670 self.labels.remove(name)
671 self._updateTimeStamp()
672 return self._getUnlabelEvent(name)
673
674 def _getLabelEvent(self, label):
675 name = 'pull_request'
676 data = {
677 'action': 'labeled',
678 'pull_request': {
679 'number': self.number,
680 'updated_at': self.updated_at,
681 'base': {
682 'ref': self.branch,
683 'repo': {
684 'full_name': self.project
685 }
686 },
687 'head': {
688 'sha': self.head_sha
689 }
690 },
691 'label': {
692 'name': label
Jan Hruban3b415922016-02-03 13:10:22 +0100693 },
694 'sender': {
695 'login': 'ghuser'
Jan Hruban16ad31f2015-11-07 14:39:07 +0100696 }
697 }
698 return (name, data)
699
700 def _getUnlabelEvent(self, label):
701 name = 'pull_request'
702 data = {
703 'action': 'unlabeled',
704 'pull_request': {
705 'number': self.number,
Jan Hruban3b415922016-02-03 13:10:22 +0100706 'title': self.subject,
Jan Hruban16ad31f2015-11-07 14:39:07 +0100707 'updated_at': self.updated_at,
708 'base': {
709 'ref': self.branch,
710 'repo': {
711 'full_name': self.project
712 }
713 },
714 'head': {
Jesse Keatingd96e5882017-01-19 13:55:50 -0800715 'sha': self.head_sha,
716 'repo': {
717 'full_name': self.project
718 }
Jan Hruban16ad31f2015-11-07 14:39:07 +0100719 }
720 },
721 'label': {
722 'name': label
Jan Hruban3b415922016-02-03 13:10:22 +0100723 },
724 'sender': {
725 'login': 'ghuser'
Jan Hruban16ad31f2015-11-07 14:39:07 +0100726 }
727 }
728 return (name, data)
729
Jesse Keatinga41566f2017-06-14 18:17:51 -0700730 def editBody(self, body):
731 self.body = body
732 self._updateTimeStamp()
733
Gregory Haynes4fc12542015-04-22 20:38:06 -0700734 def _getRepo(self):
735 repo_path = os.path.join(self.upstream_root, self.project)
736 return git.Repo(repo_path)
737
738 def _createPRRef(self):
739 repo = self._getRepo()
740 GithubChangeReference.create(
741 repo, self._getPRReference(), 'refs/tags/init')
742
Jan Hruban570d01c2016-03-10 21:51:32 +0100743 def _addCommitToRepo(self, files=[], reset=False):
Gregory Haynes4fc12542015-04-22 20:38:06 -0700744 repo = self._getRepo()
745 ref = repo.references[self._getPRReference()]
746 if reset:
Jan Hruban37615e52015-11-19 14:30:49 +0100747 self.number_of_commits = 0
Gregory Haynes4fc12542015-04-22 20:38:06 -0700748 ref.set_object('refs/tags/init')
Jan Hruban37615e52015-11-19 14:30:49 +0100749 self.number_of_commits += 1
Gregory Haynes4fc12542015-04-22 20:38:06 -0700750 repo.head.reference = ref
751 zuul.merger.merger.reset_repo_to_head(repo)
752 repo.git.clean('-x', '-f', '-d')
753
Jan Hruban570d01c2016-03-10 21:51:32 +0100754 if files:
755 fn = files[0]
756 self.files = files
757 else:
758 fn = '%s-%s' % (self.branch.replace('/', '_'), self.number)
759 self.files = [fn]
Jan Hruban37615e52015-11-19 14:30:49 +0100760 msg = self.subject + '-' + str(self.number_of_commits)
Gregory Haynes4fc12542015-04-22 20:38:06 -0700761 fn = os.path.join(repo.working_dir, fn)
762 f = open(fn, 'w')
763 with open(fn, 'w') as f:
764 f.write("test %s %s\n" %
765 (self.branch, self.number))
766 repo.index.add([fn])
767
768 self.head_sha = repo.index.commit(msg).hexsha
Jesse Keatingd96e5882017-01-19 13:55:50 -0800769 # Create an empty set of statuses for the given sha,
770 # each sha on a PR may have a status set on it
771 self.statuses[self.head_sha] = []
Gregory Haynes4fc12542015-04-22 20:38:06 -0700772 repo.head.reference = 'master'
773 zuul.merger.merger.reset_repo_to_head(repo)
774 repo.git.clean('-x', '-f', '-d')
775 repo.heads['master'].checkout()
776
777 def _updateTimeStamp(self):
778 self.updated_at = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.localtime())
779
780 def getPRHeadSha(self):
781 repo = self._getRepo()
782 return repo.references[self._getPRReference()].commit.hexsha
783
Jesse Keatingae4cd272017-01-30 17:10:44 -0800784 def addReview(self, user, state, granted_on=None):
Adam Gandelmand81dd762017-02-09 15:15:49 -0800785 gh_time_format = '%Y-%m-%dT%H:%M:%SZ'
786 # convert the timestamp to a str format that would be returned
787 # from github as 'submitted_at' in the API response
Jesse Keatingae4cd272017-01-30 17:10:44 -0800788
Adam Gandelmand81dd762017-02-09 15:15:49 -0800789 if granted_on:
790 granted_on = datetime.datetime.utcfromtimestamp(granted_on)
791 submitted_at = time.strftime(
792 gh_time_format, granted_on.timetuple())
793 else:
794 # github timestamps only down to the second, so we need to make
795 # sure reviews that tests add appear to be added over a period of
796 # time in the past and not all at once.
797 if not self.reviews:
798 # the first review happens 10 mins ago
799 offset = 600
800 else:
801 # subsequent reviews happen 1 minute closer to now
802 offset = 600 - (len(self.reviews) * 60)
803
804 granted_on = datetime.datetime.utcfromtimestamp(
805 time.time() - offset)
806 submitted_at = time.strftime(
807 gh_time_format, granted_on.timetuple())
808
Jesse Keatingae4cd272017-01-30 17:10:44 -0800809 self.reviews.append({
810 'state': state,
811 'user': {
812 'login': user,
813 'email': user + "@derp.com",
814 },
Adam Gandelmand81dd762017-02-09 15:15:49 -0800815 'submitted_at': submitted_at,
Jesse Keatingae4cd272017-01-30 17:10:44 -0800816 })
817
Gregory Haynes4fc12542015-04-22 20:38:06 -0700818 def _getPRReference(self):
819 return '%s/head' % self.number
820
821 def _getPullRequestEvent(self, action):
822 name = 'pull_request'
823 data = {
824 'action': action,
825 'number': self.number,
826 'pull_request': {
827 'number': self.number,
Jan Hruban3b415922016-02-03 13:10:22 +0100828 'title': self.subject,
Gregory Haynes4fc12542015-04-22 20:38:06 -0700829 'updated_at': self.updated_at,
830 'base': {
831 'ref': self.branch,
832 'repo': {
833 'full_name': self.project
834 }
835 },
836 'head': {
Jesse Keatingd96e5882017-01-19 13:55:50 -0800837 'sha': self.head_sha,
838 'repo': {
839 'full_name': self.project
840 }
Jesse Keatinga41566f2017-06-14 18:17:51 -0700841 },
842 'body': self.body
Jan Hruban3b415922016-02-03 13:10:22 +0100843 },
844 'sender': {
845 'login': 'ghuser'
Gregory Haynes4fc12542015-04-22 20:38:06 -0700846 }
847 }
848 return (name, data)
849
Adam Gandelman8c6eeb52017-01-23 16:31:06 -0800850 def getCommitStatusEvent(self, context, state='success', user='zuul'):
851 name = 'status'
852 data = {
853 'state': state,
854 'sha': self.head_sha,
855 'description': 'Test results for %s: %s' % (self.head_sha, state),
856 'target_url': 'http://zuul/%s' % self.head_sha,
857 'branches': [],
858 'context': context,
859 'sender': {
860 'login': user
861 }
862 }
863 return (name, data)
864
Gregory Haynes4fc12542015-04-22 20:38:06 -0700865
866class FakeGithubConnection(githubconnection.GithubConnection):
867 log = logging.getLogger("zuul.test.FakeGithubConnection")
868
869 def __init__(self, driver, connection_name, connection_config,
870 upstream_root=None):
871 super(FakeGithubConnection, self).__init__(driver, connection_name,
872 connection_config)
873 self.connection_name = connection_name
874 self.pr_number = 0
875 self.pull_requests = []
Jesse Keating1f7ebe92017-06-12 17:21:00 -0700876 self.statuses = {}
Gregory Haynes4fc12542015-04-22 20:38:06 -0700877 self.upstream_root = upstream_root
Jan Hruban49bff072015-11-03 11:45:46 +0100878 self.merge_failure = False
879 self.merge_not_allowed_count = 0
Jesse Keating08dab8f2017-06-21 12:59:23 +0100880 self.reports = []
Gregory Haynes4fc12542015-04-22 20:38:06 -0700881
Jesse Keatinga41566f2017-06-14 18:17:51 -0700882 def openFakePullRequest(self, project, branch, subject, files=[],
Jesse Keating152a4022017-07-07 08:39:52 -0700883 body=None):
Gregory Haynes4fc12542015-04-22 20:38:06 -0700884 self.pr_number += 1
885 pull_request = FakeGithubPullRequest(
Jan Hruban570d01c2016-03-10 21:51:32 +0100886 self, self.pr_number, project, branch, subject, self.upstream_root,
Jesse Keatinga41566f2017-06-14 18:17:51 -0700887 files=files, body=body)
Gregory Haynes4fc12542015-04-22 20:38:06 -0700888 self.pull_requests.append(pull_request)
889 return pull_request
890
Jesse Keating71a47ff2017-06-06 11:36:43 -0700891 def getPushEvent(self, project, ref, old_rev=None, new_rev=None,
892 added_files=[], removed_files=[], modified_files=[]):
Wayne1a78c612015-06-11 17:14:13 -0700893 if not old_rev:
894 old_rev = '00000000000000000000000000000000'
895 if not new_rev:
896 new_rev = random_sha1()
897 name = 'push'
898 data = {
899 'ref': ref,
900 'before': old_rev,
901 'after': new_rev,
902 'repository': {
903 'full_name': project
Jesse Keating71a47ff2017-06-06 11:36:43 -0700904 },
905 'commits': [
906 {
907 'added': added_files,
908 'removed': removed_files,
909 'modified': modified_files
910 }
911 ]
Wayne1a78c612015-06-11 17:14:13 -0700912 }
913 return (name, data)
914
Gregory Haynes4fc12542015-04-22 20:38:06 -0700915 def emitEvent(self, event):
916 """Emulates sending the GitHub webhook event to the connection."""
917 port = self.webapp.server.socket.getsockname()[1]
918 name, data = event
Clint Byrum607d10e2017-05-18 12:05:13 -0700919 payload = json.dumps(data).encode('utf8')
Gregory Haynes4fc12542015-04-22 20:38:06 -0700920 headers = {'X-Github-Event': name}
921 req = urllib.request.Request(
922 'http://localhost:%s/connection/%s/payload'
923 % (port, self.connection_name),
924 data=payload, headers=headers)
Tristan Cacqueray2bafb1f2017-06-12 07:10:26 +0000925 return urllib.request.urlopen(req)
Gregory Haynes4fc12542015-04-22 20:38:06 -0700926
Jan Hrubanc7ab1602015-10-14 15:29:33 +0200927 def getPull(self, project, number):
928 pr = self.pull_requests[number - 1]
929 data = {
930 'number': number,
Jan Hruban3b415922016-02-03 13:10:22 +0100931 'title': pr.subject,
Jan Hrubanc7ab1602015-10-14 15:29:33 +0200932 'updated_at': pr.updated_at,
933 'base': {
934 'repo': {
935 'full_name': pr.project
936 },
937 'ref': pr.branch,
938 },
Jan Hruban37615e52015-11-19 14:30:49 +0100939 'mergeable': True,
Jesse Keating4a27f132017-05-25 16:44:01 -0700940 'state': pr.state,
Jan Hrubanc7ab1602015-10-14 15:29:33 +0200941 'head': {
Jesse Keatingd96e5882017-01-19 13:55:50 -0800942 'sha': pr.head_sha,
943 'repo': {
944 'full_name': pr.project
945 }
Jesse Keating61040e72017-06-08 15:08:27 -0700946 },
Jesse Keating19dfb492017-06-13 12:32:33 -0700947 'files': pr.files,
Jesse Keatinga41566f2017-06-14 18:17:51 -0700948 'labels': pr.labels,
949 'merged': pr.is_merged,
950 'body': pr.body
Jan Hrubanc7ab1602015-10-14 15:29:33 +0200951 }
952 return data
953
Adam Gandelman8c6eeb52017-01-23 16:31:06 -0800954 def getPullBySha(self, sha):
955 prs = list(set([p for p in self.pull_requests if sha == p.head_sha]))
956 if len(prs) > 1:
957 raise Exception('Multiple pulls found with head sha: %s' % sha)
958 pr = prs[0]
959 return self.getPull(pr.project, pr.number)
960
Jesse Keatingae4cd272017-01-30 17:10:44 -0800961 def _getPullReviews(self, owner, project, number):
962 pr = self.pull_requests[number - 1]
963 return pr.reviews
964
Jan Hruban3b415922016-02-03 13:10:22 +0100965 def getUser(self, login):
966 data = {
967 'username': login,
968 'name': 'Github User',
969 'email': 'github.user@example.com'
970 }
971 return data
972
Jesse Keatingae4cd272017-01-30 17:10:44 -0800973 def getRepoPermission(self, project, login):
974 owner, proj = project.split('/')
975 for pr in self.pull_requests:
976 pr_owner, pr_project = pr.project.split('/')
977 if (pr_owner == owner and proj == pr_project):
978 if login in pr.writers:
979 return 'write'
980 else:
981 return 'read'
982
Gregory Haynes4fc12542015-04-22 20:38:06 -0700983 def getGitUrl(self, project):
984 return os.path.join(self.upstream_root, str(project))
985
Jan Hruban6d53c5e2015-10-24 03:03:34 +0200986 def real_getGitUrl(self, project):
987 return super(FakeGithubConnection, self).getGitUrl(project)
988
Gregory Haynes4fc12542015-04-22 20:38:06 -0700989 def getProjectBranches(self, project):
990 """Masks getProjectBranches since we don't have a real github"""
991
992 # just returns master for now
993 return ['master']
994
Jan Hrubane252a732017-01-03 15:03:09 +0100995 def commentPull(self, project, pr_number, message):
Jesse Keating08dab8f2017-06-21 12:59:23 +0100996 # record that this got reported
997 self.reports.append((project, pr_number, 'comment'))
Wayne40f40042015-06-12 16:56:30 -0700998 pull_request = self.pull_requests[pr_number - 1]
999 pull_request.addComment(message)
1000
Jan Hruban3b415922016-02-03 13:10:22 +01001001 def mergePull(self, project, pr_number, commit_message='', sha=None):
Jesse Keating08dab8f2017-06-21 12:59:23 +01001002 # record that this got reported
1003 self.reports.append((project, pr_number, 'merge'))
Jan Hruban49bff072015-11-03 11:45:46 +01001004 pull_request = self.pull_requests[pr_number - 1]
1005 if self.merge_failure:
1006 raise Exception('Pull request was not merged')
1007 if self.merge_not_allowed_count > 0:
1008 self.merge_not_allowed_count -= 1
1009 raise MergeFailure('Merge was not successful due to mergeability'
1010 ' conflict')
1011 pull_request.is_merged = True
Jan Hruban3b415922016-02-03 13:10:22 +01001012 pull_request.merge_message = commit_message
Jan Hruban49bff072015-11-03 11:45:46 +01001013
Jesse Keatingd96e5882017-01-19 13:55:50 -08001014 def getCommitStatuses(self, project, sha):
Jesse Keating1f7ebe92017-06-12 17:21:00 -07001015 return self.statuses.get(project, {}).get(sha, [])
Jesse Keatingd96e5882017-01-19 13:55:50 -08001016
Jesse Keating1f7ebe92017-06-12 17:21:00 -07001017 def setCommitStatus(self, project, sha, state, url='', description='',
1018 context='default', user='zuul'):
Jesse Keating08dab8f2017-06-21 12:59:23 +01001019 # record that this got reported
1020 self.reports.append((project, sha, 'status', (user, context, state)))
Jesse Keating1f7ebe92017-06-12 17:21:00 -07001021 # always insert a status to the front of the list, to represent
1022 # the last status provided for a commit.
1023 # Since we're bypassing github API, which would require a user, we
1024 # default the user as 'zuul' here.
1025 self.statuses.setdefault(project, {}).setdefault(sha, [])
1026 self.statuses[project][sha].insert(0, {
1027 'state': state,
1028 'url': url,
1029 'description': description,
1030 'context': context,
1031 'creator': {
1032 'login': user
1033 }
1034 })
Jan Hrubane252a732017-01-03 15:03:09 +01001035
Jan Hruban16ad31f2015-11-07 14:39:07 +01001036 def labelPull(self, project, pr_number, label):
Jesse Keating08dab8f2017-06-21 12:59:23 +01001037 # record that this got reported
1038 self.reports.append((project, pr_number, 'label', label))
Jan Hruban16ad31f2015-11-07 14:39:07 +01001039 pull_request = self.pull_requests[pr_number - 1]
1040 pull_request.addLabel(label)
1041
1042 def unlabelPull(self, project, pr_number, label):
Jesse Keating08dab8f2017-06-21 12:59:23 +01001043 # record that this got reported
1044 self.reports.append((project, pr_number, 'unlabel', label))
Jan Hruban16ad31f2015-11-07 14:39:07 +01001045 pull_request = self.pull_requests[pr_number - 1]
1046 pull_request.removeLabel(label)
1047
Jesse Keatinga41566f2017-06-14 18:17:51 -07001048 def _getNeededByFromPR(self, change):
1049 prs = []
1050 pattern = re.compile(r"Depends-On.*https://%s/%s/pull/%s" %
James E. Blair5f11ff32017-06-23 21:46:45 +01001051 (self.server, change.project.name,
Jesse Keatinga41566f2017-06-14 18:17:51 -07001052 change.number))
1053 for pr in self.pull_requests:
Jesse Keating152a4022017-07-07 08:39:52 -07001054 if not pr.body:
1055 body = ''
1056 else:
1057 body = pr.body
1058 if pattern.search(body):
Jesse Keatinga41566f2017-06-14 18:17:51 -07001059 # Get our version of a pull so that it's a dict
1060 pull = self.getPull(pr.project, pr.number)
1061 prs.append(pull)
1062
1063 return prs
1064
Gregory Haynes4fc12542015-04-22 20:38:06 -07001065
Clark Boylanb640e052014-04-03 16:41:46 -07001066class BuildHistory(object):
1067 def __init__(self, **kw):
1068 self.__dict__.update(kw)
1069
1070 def __repr__(self):
James E. Blair21037782017-07-19 11:56:55 -07001071 return ("<Completed build, result: %s name: %s uuid: %s "
1072 "changes: %s ref: %s>" %
1073 (self.result, self.name, self.uuid,
1074 self.changes, self.ref))
Clark Boylanb640e052014-04-03 16:41:46 -07001075
1076
Clark Boylanb640e052014-04-03 16:41:46 -07001077class FakeStatsd(threading.Thread):
1078 def __init__(self):
1079 threading.Thread.__init__(self)
1080 self.daemon = True
1081 self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
1082 self.sock.bind(('', 0))
1083 self.port = self.sock.getsockname()[1]
1084 self.wake_read, self.wake_write = os.pipe()
1085 self.stats = []
1086
1087 def run(self):
1088 while True:
1089 poll = select.poll()
1090 poll.register(self.sock, select.POLLIN)
1091 poll.register(self.wake_read, select.POLLIN)
1092 ret = poll.poll()
1093 for (fd, event) in ret:
1094 if fd == self.sock.fileno():
1095 data = self.sock.recvfrom(1024)
1096 if not data:
1097 return
1098 self.stats.append(data[0])
1099 if fd == self.wake_read:
1100 return
1101
1102 def stop(self):
Clint Byrumf322fe22017-05-10 20:53:12 -07001103 os.write(self.wake_write, b'1\n')
Clark Boylanb640e052014-04-03 16:41:46 -07001104
1105
James E. Blaire1767bc2016-08-02 10:00:27 -07001106class FakeBuild(object):
Clark Boylanb640e052014-04-03 16:41:46 -07001107 log = logging.getLogger("zuul.test")
1108
Paul Belanger174a8272017-03-14 13:20:10 -04001109 def __init__(self, executor_server, job):
Clark Boylanb640e052014-04-03 16:41:46 -07001110 self.daemon = True
Paul Belanger174a8272017-03-14 13:20:10 -04001111 self.executor_server = executor_server
Clark Boylanb640e052014-04-03 16:41:46 -07001112 self.job = job
James E. Blairab7132b2016-08-05 12:36:22 -07001113 self.jobdir = None
James E. Blair17302972016-08-10 16:11:42 -07001114 self.uuid = job.unique
Clark Boylanb640e052014-04-03 16:41:46 -07001115 self.parameters = json.loads(job.arguments)
James E. Blair16d96a02017-06-08 11:32:56 -07001116 # TODOv3(jeblair): self.node is really "the label of the node
1117 # assigned". We should rename it (self.node_label?) if we
James E. Blair34776ee2016-08-25 13:53:54 -07001118 # keep using it like this, or we may end up exposing more of
1119 # the complexity around multi-node jobs here
James E. Blair16d96a02017-06-08 11:32:56 -07001120 # (self.nodes[0].label?)
James E. Blair34776ee2016-08-25 13:53:54 -07001121 self.node = None
1122 if len(self.parameters.get('nodes')) == 1:
James E. Blair16d96a02017-06-08 11:32:56 -07001123 self.node = self.parameters['nodes'][0]['label']
James E. Blair74f101b2017-07-21 15:32:01 -07001124 self.unique = self.parameters['zuul']['build']
James E. Blaire675d682017-07-21 15:29:35 -07001125 self.pipeline = self.parameters['zuul']['pipeline']
James E. Blaire5366092017-07-21 15:30:39 -07001126 self.project = self.parameters['zuul']['project']['name']
James E. Blair3f876d52016-07-22 13:07:14 -07001127 self.name = self.parameters['job']
Clark Boylanb640e052014-04-03 16:41:46 -07001128 self.wait_condition = threading.Condition()
1129 self.waiting = False
1130 self.aborted = False
Paul Belanger71d98172016-11-08 10:56:31 -05001131 self.requeue = False
Clark Boylanb640e052014-04-03 16:41:46 -07001132 self.created = time.time()
James E. Blaire1767bc2016-08-02 10:00:27 -07001133 self.changes = None
James E. Blair6193a1f2017-07-21 15:13:15 -07001134 items = self.parameters['zuul']['items']
1135 self.changes = ' '.join(['%s,%s' % (x['change'], x['patchset'])
1136 for x in items if 'change' in x])
Clark Boylanb640e052014-04-03 16:41:46 -07001137
James E. Blair3158e282016-08-19 09:34:11 -07001138 def __repr__(self):
1139 waiting = ''
1140 if self.waiting:
1141 waiting = ' [waiting]'
Jamie Lennoxd2e37332016-12-05 15:26:19 +11001142 return '<FakeBuild %s:%s %s%s>' % (self.pipeline, self.name,
1143 self.changes, waiting)
James E. Blair3158e282016-08-19 09:34:11 -07001144
Clark Boylanb640e052014-04-03 16:41:46 -07001145 def release(self):
James E. Blaire7b99a02016-08-05 14:27:34 -07001146 """Release this build."""
Clark Boylanb640e052014-04-03 16:41:46 -07001147 self.wait_condition.acquire()
1148 self.wait_condition.notify()
1149 self.waiting = False
1150 self.log.debug("Build %s released" % self.unique)
1151 self.wait_condition.release()
1152
1153 def isWaiting(self):
James E. Blaire7b99a02016-08-05 14:27:34 -07001154 """Return whether this build is being held.
1155
1156 :returns: Whether the build is being held.
1157 :rtype: bool
1158 """
1159
Clark Boylanb640e052014-04-03 16:41:46 -07001160 self.wait_condition.acquire()
1161 if self.waiting:
1162 ret = True
1163 else:
1164 ret = False
1165 self.wait_condition.release()
1166 return ret
1167
1168 def _wait(self):
1169 self.wait_condition.acquire()
1170 self.waiting = True
1171 self.log.debug("Build %s waiting" % self.unique)
1172 self.wait_condition.wait()
1173 self.wait_condition.release()
1174
1175 def run(self):
Clark Boylanb640e052014-04-03 16:41:46 -07001176 self.log.debug('Running build %s' % self.unique)
1177
Paul Belanger174a8272017-03-14 13:20:10 -04001178 if self.executor_server.hold_jobs_in_build:
Clark Boylanb640e052014-04-03 16:41:46 -07001179 self.log.debug('Holding build %s' % self.unique)
1180 self._wait()
1181 self.log.debug("Build %s continuing" % self.unique)
1182
James E. Blair412fba82017-01-26 15:00:50 -08001183 result = (RecordingAnsibleJob.RESULT_NORMAL, 0) # Success
James E. Blair247cab72017-07-20 16:52:36 -07001184 if self.shouldFail():
James E. Blair412fba82017-01-26 15:00:50 -08001185 result = (RecordingAnsibleJob.RESULT_NORMAL, 1) # Failure
Clark Boylanb640e052014-04-03 16:41:46 -07001186 if self.aborted:
James E. Blair412fba82017-01-26 15:00:50 -08001187 result = (RecordingAnsibleJob.RESULT_ABORTED, None)
Paul Belanger71d98172016-11-08 10:56:31 -05001188 if self.requeue:
James E. Blair412fba82017-01-26 15:00:50 -08001189 result = (RecordingAnsibleJob.RESULT_UNREACHABLE, None)
Clark Boylanb640e052014-04-03 16:41:46 -07001190
James E. Blaire1767bc2016-08-02 10:00:27 -07001191 return result
Clark Boylanb640e052014-04-03 16:41:46 -07001192
James E. Blaira5dba232016-08-08 15:53:24 -07001193 def shouldFail(self):
Paul Belanger174a8272017-03-14 13:20:10 -04001194 changes = self.executor_server.fail_tests.get(self.name, [])
James E. Blaira5dba232016-08-08 15:53:24 -07001195 for change in changes:
1196 if self.hasChanges(change):
1197 return True
1198 return False
1199
James E. Blaire7b99a02016-08-05 14:27:34 -07001200 def hasChanges(self, *changes):
1201 """Return whether this build has certain changes in its git repos.
1202
1203 :arg FakeChange changes: One or more changes (varargs) that
Clark Boylan500992b2017-04-03 14:28:24 -07001204 are expected to be present (in order) in the git repository of
1205 the active project.
James E. Blaire7b99a02016-08-05 14:27:34 -07001206
1207 :returns: Whether the build has the indicated changes.
1208 :rtype: bool
1209
1210 """
Clint Byrum3343e3e2016-11-15 16:05:03 -08001211 for change in changes:
Gregory Haynes4fc12542015-04-22 20:38:06 -07001212 hostname = change.source.canonical_hostname
James E. Blair2a535672017-04-27 12:03:15 -07001213 path = os.path.join(self.jobdir.src_root, hostname, change.project)
Clint Byrum3343e3e2016-11-15 16:05:03 -08001214 try:
1215 repo = git.Repo(path)
1216 except NoSuchPathError as e:
1217 self.log.debug('%s' % e)
1218 return False
James E. Blair247cab72017-07-20 16:52:36 -07001219 repo_messages = [c.message.strip() for c in repo.iter_commits()]
Clint Byrum3343e3e2016-11-15 16:05:03 -08001220 commit_message = '%s-1' % change.subject
1221 self.log.debug("Checking if build %s has changes; commit_message "
1222 "%s; repo_messages %s" % (self, commit_message,
1223 repo_messages))
1224 if commit_message not in repo_messages:
James E. Blair962220f2016-08-03 11:22:38 -07001225 self.log.debug(" messages do not match")
1226 return False
1227 self.log.debug(" OK")
1228 return True
1229
James E. Blaird8af5422017-05-24 13:59:40 -07001230 def getWorkspaceRepos(self, projects):
1231 """Return workspace git repo objects for the listed projects
1232
1233 :arg list projects: A list of strings, each the canonical name
1234 of a project.
1235
1236 :returns: A dictionary of {name: repo} for every listed
1237 project.
1238 :rtype: dict
1239
1240 """
1241
1242 repos = {}
1243 for project in projects:
1244 path = os.path.join(self.jobdir.src_root, project)
1245 repo = git.Repo(path)
1246 repos[project] = repo
1247 return repos
1248
Clark Boylanb640e052014-04-03 16:41:46 -07001249
Paul Belanger174a8272017-03-14 13:20:10 -04001250class RecordingExecutorServer(zuul.executor.server.ExecutorServer):
1251 """An Ansible executor to be used in tests.
James E. Blaire7b99a02016-08-05 14:27:34 -07001252
Paul Belanger174a8272017-03-14 13:20:10 -04001253 :ivar bool hold_jobs_in_build: If true, when jobs are executed
James E. Blaire7b99a02016-08-05 14:27:34 -07001254 they will report that they have started but then pause until
1255 released before reporting completion. This attribute may be
1256 changed at any time and will take effect for subsequently
Paul Belanger174a8272017-03-14 13:20:10 -04001257 executed builds, but previously held builds will still need to
James E. Blaire7b99a02016-08-05 14:27:34 -07001258 be explicitly released.
1259
1260 """
James E. Blairf5dbd002015-12-23 15:26:17 -08001261 def __init__(self, *args, **kw):
James E. Blaire1767bc2016-08-02 10:00:27 -07001262 self._run_ansible = kw.pop('_run_ansible', False)
James E. Blaira92cbc82017-01-23 14:56:49 -08001263 self._test_root = kw.pop('_test_root', False)
Paul Belanger174a8272017-03-14 13:20:10 -04001264 super(RecordingExecutorServer, self).__init__(*args, **kw)
James E. Blaire1767bc2016-08-02 10:00:27 -07001265 self.hold_jobs_in_build = False
1266 self.lock = threading.Lock()
1267 self.running_builds = []
James E. Blair3f876d52016-07-22 13:07:14 -07001268 self.build_history = []
James E. Blaire1767bc2016-08-02 10:00:27 -07001269 self.fail_tests = {}
James E. Blairab7132b2016-08-05 12:36:22 -07001270 self.job_builds = {}
James E. Blairf5dbd002015-12-23 15:26:17 -08001271
James E. Blaira5dba232016-08-08 15:53:24 -07001272 def failJob(self, name, change):
Paul Belanger174a8272017-03-14 13:20:10 -04001273 """Instruct the executor to report matching builds as failures.
James E. Blaire7b99a02016-08-05 14:27:34 -07001274
1275 :arg str name: The name of the job to fail.
James E. Blaira5dba232016-08-08 15:53:24 -07001276 :arg Change change: The :py:class:`~tests.base.FakeChange`
1277 instance which should cause the job to fail. This job
1278 will also fail for changes depending on this change.
James E. Blaire7b99a02016-08-05 14:27:34 -07001279
1280 """
James E. Blaire1767bc2016-08-02 10:00:27 -07001281 l = self.fail_tests.get(name, [])
1282 l.append(change)
1283 self.fail_tests[name] = l
James E. Blairf5dbd002015-12-23 15:26:17 -08001284
James E. Blair962220f2016-08-03 11:22:38 -07001285 def release(self, regex=None):
James E. Blaire7b99a02016-08-05 14:27:34 -07001286 """Release a held build.
1287
1288 :arg str regex: A regular expression which, if supplied, will
1289 cause only builds with matching names to be released. If
1290 not supplied, all builds will be released.
1291
1292 """
James E. Blair962220f2016-08-03 11:22:38 -07001293 builds = self.running_builds[:]
1294 self.log.debug("Releasing build %s (%s)" % (regex,
1295 len(self.running_builds)))
1296 for build in builds:
1297 if not regex or re.match(regex, build.name):
1298 self.log.debug("Releasing build %s" %
James E. Blair74f101b2017-07-21 15:32:01 -07001299 (build.parameters['zuul']['build']))
James E. Blair962220f2016-08-03 11:22:38 -07001300 build.release()
1301 else:
1302 self.log.debug("Not releasing build %s" %
James E. Blair74f101b2017-07-21 15:32:01 -07001303 (build.parameters['zuul']['build']))
James E. Blair962220f2016-08-03 11:22:38 -07001304 self.log.debug("Done releasing builds %s (%s)" %
1305 (regex, len(self.running_builds)))
1306
Paul Belanger174a8272017-03-14 13:20:10 -04001307 def executeJob(self, job):
James E. Blair34776ee2016-08-25 13:53:54 -07001308 build = FakeBuild(self, job)
James E. Blaire1767bc2016-08-02 10:00:27 -07001309 job.build = build
James E. Blaire1767bc2016-08-02 10:00:27 -07001310 self.running_builds.append(build)
James E. Blairab7132b2016-08-05 12:36:22 -07001311 self.job_builds[job.unique] = build
James E. Blaira92cbc82017-01-23 14:56:49 -08001312 args = json.loads(job.arguments)
Monty Taylord13bc362017-06-30 13:11:37 -05001313 args['zuul']['_test'] = dict(test_root=self._test_root)
James E. Blaira92cbc82017-01-23 14:56:49 -08001314 job.arguments = json.dumps(args)
Joshua Hesketh50c21782016-10-13 21:34:14 +11001315 self.job_workers[job.unique] = RecordingAnsibleJob(self, job)
1316 self.job_workers[job.unique].run()
James E. Blair17302972016-08-10 16:11:42 -07001317
1318 def stopJob(self, job):
1319 self.log.debug("handle stop")
1320 parameters = json.loads(job.arguments)
1321 uuid = parameters['uuid']
1322 for build in self.running_builds:
1323 if build.unique == uuid:
1324 build.aborted = True
1325 build.release()
Paul Belanger174a8272017-03-14 13:20:10 -04001326 super(RecordingExecutorServer, self).stopJob(job)
James E. Blairab7132b2016-08-05 12:36:22 -07001327
James E. Blaira002b032017-04-18 10:35:48 -07001328 def stop(self):
1329 for build in self.running_builds:
1330 build.release()
1331 super(RecordingExecutorServer, self).stop()
1332
Joshua Hesketh50c21782016-10-13 21:34:14 +11001333
Paul Belanger174a8272017-03-14 13:20:10 -04001334class RecordingAnsibleJob(zuul.executor.server.AnsibleJob):
James E. Blairf327c572017-05-24 13:58:42 -07001335 def doMergeChanges(self, merger, items, repo_state):
K Jonathan Harkerae04e4c2017-03-15 19:07:11 -07001336 # Get a merger in order to update the repos involved in this job.
James E. Blair1960d682017-04-28 15:44:14 -07001337 commit = super(RecordingAnsibleJob, self).doMergeChanges(
James E. Blairf327c572017-05-24 13:58:42 -07001338 merger, items, repo_state)
K Jonathan Harkerae04e4c2017-03-15 19:07:11 -07001339 if not commit: # merge conflict
1340 self.recordResult('MERGER_FAILURE')
1341 return commit
1342
1343 def recordResult(self, result):
Paul Belanger174a8272017-03-14 13:20:10 -04001344 build = self.executor_server.job_builds[self.job.unique]
Paul Belanger174a8272017-03-14 13:20:10 -04001345 self.executor_server.lock.acquire()
1346 self.executor_server.build_history.append(
James E. Blair17302972016-08-10 16:11:42 -07001347 BuildHistory(name=build.name, result=result, changes=build.changes,
1348 node=build.node, uuid=build.unique,
James E. Blair21037782017-07-19 11:56:55 -07001349 ref=build.parameters['zuul']['ref'],
K Jonathan Harker2c1a6232017-02-21 14:34:08 -08001350 parameters=build.parameters, jobdir=build.jobdir,
James E. Blaire675d682017-07-21 15:29:35 -07001351 pipeline=build.parameters['zuul']['pipeline'])
James E. Blaire1767bc2016-08-02 10:00:27 -07001352 )
Paul Belanger174a8272017-03-14 13:20:10 -04001353 self.executor_server.running_builds.remove(build)
1354 del self.executor_server.job_builds[self.job.unique]
1355 self.executor_server.lock.release()
K Jonathan Harkerae04e4c2017-03-15 19:07:11 -07001356
1357 def runPlaybooks(self, args):
1358 build = self.executor_server.job_builds[self.job.unique]
1359 build.jobdir = self.jobdir
1360
1361 result = super(RecordingAnsibleJob, self).runPlaybooks(args)
1362 self.recordResult(result)
James E. Blair412fba82017-01-26 15:00:50 -08001363 return result
1364
James E. Blair74a82cf2017-07-12 17:23:08 -07001365 def runAnsible(self, cmd, timeout, config_file, trusted):
Paul Belanger174a8272017-03-14 13:20:10 -04001366 build = self.executor_server.job_builds[self.job.unique]
James E. Blair412fba82017-01-26 15:00:50 -08001367
Paul Belanger174a8272017-03-14 13:20:10 -04001368 if self.executor_server._run_ansible:
Monty Taylorc231d932017-02-03 09:57:15 -06001369 result = super(RecordingAnsibleJob, self).runAnsible(
James E. Blair74a82cf2017-07-12 17:23:08 -07001370 cmd, timeout, config_file, trusted)
James E. Blair412fba82017-01-26 15:00:50 -08001371 else:
1372 result = build.run()
James E. Blaire1767bc2016-08-02 10:00:27 -07001373 return result
James E. Blairf5dbd002015-12-23 15:26:17 -08001374
James E. Blairad8dca02017-02-21 11:48:32 -05001375 def getHostList(self, args):
1376 self.log.debug("hostlist")
1377 hosts = super(RecordingAnsibleJob, self).getHostList(args)
Paul Belangerc5bf3752017-03-16 19:38:43 -04001378 for host in hosts:
1379 host['host_vars']['ansible_connection'] = 'local'
1380
1381 hosts.append(dict(
1382 name='localhost',
1383 host_vars=dict(ansible_connection='local'),
1384 host_keys=[]))
James E. Blairad8dca02017-02-21 11:48:32 -05001385 return hosts
1386
James E. Blairf5dbd002015-12-23 15:26:17 -08001387
Clark Boylanb640e052014-04-03 16:41:46 -07001388class FakeGearmanServer(gear.Server):
James E. Blaire7b99a02016-08-05 14:27:34 -07001389 """A Gearman server for use in tests.
1390
1391 :ivar bool hold_jobs_in_queue: If true, submitted jobs will be
1392 added to the queue but will not be distributed to workers
1393 until released. This attribute may be changed at any time and
1394 will take effect for subsequently enqueued jobs, but
1395 previously held jobs will still need to be explicitly
1396 released.
1397
1398 """
1399
Paul Belanger0a21f0a2017-06-13 13:14:42 -04001400 def __init__(self, use_ssl=False):
Clark Boylanb640e052014-04-03 16:41:46 -07001401 self.hold_jobs_in_queue = False
Paul Belanger0a21f0a2017-06-13 13:14:42 -04001402 if use_ssl:
1403 ssl_ca = os.path.join(FIXTURE_DIR, 'gearman/root-ca.pem')
1404 ssl_cert = os.path.join(FIXTURE_DIR, 'gearman/server.pem')
1405 ssl_key = os.path.join(FIXTURE_DIR, 'gearman/server.key')
1406 else:
1407 ssl_ca = None
1408 ssl_cert = None
1409 ssl_key = None
1410
1411 super(FakeGearmanServer, self).__init__(0, ssl_key=ssl_key,
1412 ssl_cert=ssl_cert,
1413 ssl_ca=ssl_ca)
Clark Boylanb640e052014-04-03 16:41:46 -07001414
1415 def getJobForConnection(self, connection, peek=False):
Monty Taylorb934c1a2017-06-16 19:31:47 -05001416 for job_queue in [self.high_queue, self.normal_queue, self.low_queue]:
1417 for job in job_queue:
Clark Boylanb640e052014-04-03 16:41:46 -07001418 if not hasattr(job, 'waiting'):
Clint Byrumf322fe22017-05-10 20:53:12 -07001419 if job.name.startswith(b'executor:execute'):
Clark Boylanb640e052014-04-03 16:41:46 -07001420 job.waiting = self.hold_jobs_in_queue
1421 else:
1422 job.waiting = False
1423 if job.waiting:
1424 continue
1425 if job.name in connection.functions:
1426 if not peek:
Monty Taylorb934c1a2017-06-16 19:31:47 -05001427 job_queue.remove(job)
Clark Boylanb640e052014-04-03 16:41:46 -07001428 connection.related_jobs[job.handle] = job
1429 job.worker_connection = connection
1430 job.running = True
1431 return job
1432 return None
1433
1434 def release(self, regex=None):
James E. Blaire7b99a02016-08-05 14:27:34 -07001435 """Release a held job.
1436
1437 :arg str regex: A regular expression which, if supplied, will
1438 cause only jobs with matching names to be released. If
1439 not supplied, all jobs will be released.
1440 """
Clark Boylanb640e052014-04-03 16:41:46 -07001441 released = False
1442 qlen = (len(self.high_queue) + len(self.normal_queue) +
1443 len(self.low_queue))
1444 self.log.debug("releasing queued job %s (%s)" % (regex, qlen))
1445 for job in self.getQueue():
Clint Byrum03454a52017-05-26 17:14:02 -07001446 if job.name != b'executor:execute':
Clark Boylanb640e052014-04-03 16:41:46 -07001447 continue
Clint Byrum03454a52017-05-26 17:14:02 -07001448 parameters = json.loads(job.arguments.decode('utf8'))
Paul Belanger6ab6af72016-11-06 11:32:59 -05001449 if not regex or re.match(regex, parameters.get('job')):
Clark Boylanb640e052014-04-03 16:41:46 -07001450 self.log.debug("releasing queued job %s" %
1451 job.unique)
1452 job.waiting = False
1453 released = True
1454 else:
1455 self.log.debug("not releasing queued job %s" %
1456 job.unique)
1457 if released:
1458 self.wakeConnections()
1459 qlen = (len(self.high_queue) + len(self.normal_queue) +
1460 len(self.low_queue))
1461 self.log.debug("done releasing queued jobs %s (%s)" % (regex, qlen))
1462
1463
1464class FakeSMTP(object):
1465 log = logging.getLogger('zuul.FakeSMTP')
1466
1467 def __init__(self, messages, server, port):
1468 self.server = server
1469 self.port = port
1470 self.messages = messages
1471
1472 def sendmail(self, from_email, to_email, msg):
1473 self.log.info("Sending email from %s, to %s, with msg %s" % (
1474 from_email, to_email, msg))
1475
1476 headers = msg.split('\n\n', 1)[0]
1477 body = msg.split('\n\n', 1)[1]
1478
1479 self.messages.append(dict(
1480 from_email=from_email,
1481 to_email=to_email,
1482 msg=msg,
1483 headers=headers,
1484 body=body,
1485 ))
1486
1487 return True
1488
1489 def quit(self):
1490 return True
1491
1492
James E. Blairdce6cea2016-12-20 16:45:32 -08001493class FakeNodepool(object):
1494 REQUEST_ROOT = '/nodepool/requests'
James E. Blaire18d4602017-01-05 11:17:28 -08001495 NODE_ROOT = '/nodepool/nodes'
James E. Blairdce6cea2016-12-20 16:45:32 -08001496
1497 log = logging.getLogger("zuul.test.FakeNodepool")
1498
1499 def __init__(self, host, port, chroot):
1500 self.client = kazoo.client.KazooClient(
1501 hosts='%s:%s%s' % (host, port, chroot))
1502 self.client.start()
1503 self._running = True
James E. Blair15be0e12017-01-03 13:45:20 -08001504 self.paused = False
James E. Blairdce6cea2016-12-20 16:45:32 -08001505 self.thread = threading.Thread(target=self.run)
1506 self.thread.daemon = True
1507 self.thread.start()
James E. Blair6ab79e02017-01-06 10:10:17 -08001508 self.fail_requests = set()
James E. Blairdce6cea2016-12-20 16:45:32 -08001509
1510 def stop(self):
1511 self._running = False
1512 self.thread.join()
1513 self.client.stop()
1514 self.client.close()
1515
1516 def run(self):
1517 while self._running:
James E. Blaircbbce0d2017-05-19 07:28:29 -07001518 try:
1519 self._run()
1520 except Exception:
1521 self.log.exception("Error in fake nodepool:")
James E. Blairdce6cea2016-12-20 16:45:32 -08001522 time.sleep(0.1)
1523
1524 def _run(self):
James E. Blair15be0e12017-01-03 13:45:20 -08001525 if self.paused:
1526 return
James E. Blairdce6cea2016-12-20 16:45:32 -08001527 for req in self.getNodeRequests():
1528 self.fulfillRequest(req)
1529
1530 def getNodeRequests(self):
1531 try:
1532 reqids = self.client.get_children(self.REQUEST_ROOT)
1533 except kazoo.exceptions.NoNodeError:
1534 return []
1535 reqs = []
1536 for oid in sorted(reqids):
1537 path = self.REQUEST_ROOT + '/' + oid
James E. Blair0ef64f82017-02-02 11:25:16 -08001538 try:
1539 data, stat = self.client.get(path)
Clint Byrumf322fe22017-05-10 20:53:12 -07001540 data = json.loads(data.decode('utf8'))
James E. Blair0ef64f82017-02-02 11:25:16 -08001541 data['_oid'] = oid
1542 reqs.append(data)
1543 except kazoo.exceptions.NoNodeError:
1544 pass
James E. Blairdce6cea2016-12-20 16:45:32 -08001545 return reqs
1546
James E. Blaire18d4602017-01-05 11:17:28 -08001547 def getNodes(self):
1548 try:
1549 nodeids = self.client.get_children(self.NODE_ROOT)
1550 except kazoo.exceptions.NoNodeError:
1551 return []
1552 nodes = []
1553 for oid in sorted(nodeids):
1554 path = self.NODE_ROOT + '/' + oid
1555 data, stat = self.client.get(path)
Clint Byrumf322fe22017-05-10 20:53:12 -07001556 data = json.loads(data.decode('utf8'))
James E. Blaire18d4602017-01-05 11:17:28 -08001557 data['_oid'] = oid
1558 try:
1559 lockfiles = self.client.get_children(path + '/lock')
1560 except kazoo.exceptions.NoNodeError:
1561 lockfiles = []
1562 if lockfiles:
1563 data['_lock'] = True
1564 else:
1565 data['_lock'] = False
1566 nodes.append(data)
1567 return nodes
1568
James E. Blaira38c28e2017-01-04 10:33:20 -08001569 def makeNode(self, request_id, node_type):
1570 now = time.time()
1571 path = '/nodepool/nodes/'
1572 data = dict(type=node_type,
1573 provider='test-provider',
1574 region='test-region',
Paul Belanger30ba93a2017-03-16 16:28:10 -04001575 az='test-az',
Monty Taylor56f61332017-04-11 05:38:12 -05001576 interface_ip='127.0.0.1',
James E. Blaira38c28e2017-01-04 10:33:20 -08001577 public_ipv4='127.0.0.1',
1578 private_ipv4=None,
1579 public_ipv6=None,
1580 allocated_to=request_id,
1581 state='ready',
1582 state_time=now,
1583 created_time=now,
1584 updated_time=now,
1585 image_id=None,
Paul Belangerc5bf3752017-03-16 19:38:43 -04001586 host_keys=["fake-key1", "fake-key2"],
Paul Belanger174a8272017-03-14 13:20:10 -04001587 executor='fake-nodepool')
Clint Byrumf322fe22017-05-10 20:53:12 -07001588 data = json.dumps(data).encode('utf8')
James E. Blaira38c28e2017-01-04 10:33:20 -08001589 path = self.client.create(path, data,
1590 makepath=True,
1591 sequence=True)
1592 nodeid = path.split("/")[-1]
1593 return nodeid
1594
James E. Blair6ab79e02017-01-06 10:10:17 -08001595 def addFailRequest(self, request):
1596 self.fail_requests.add(request['_oid'])
1597
James E. Blairdce6cea2016-12-20 16:45:32 -08001598 def fulfillRequest(self, request):
James E. Blair6ab79e02017-01-06 10:10:17 -08001599 if request['state'] != 'requested':
James E. Blairdce6cea2016-12-20 16:45:32 -08001600 return
1601 request = request.copy()
James E. Blairdce6cea2016-12-20 16:45:32 -08001602 oid = request['_oid']
1603 del request['_oid']
James E. Blaira38c28e2017-01-04 10:33:20 -08001604
James E. Blair6ab79e02017-01-06 10:10:17 -08001605 if oid in self.fail_requests:
1606 request['state'] = 'failed'
1607 else:
1608 request['state'] = 'fulfilled'
1609 nodes = []
1610 for node in request['node_types']:
1611 nodeid = self.makeNode(oid, node)
1612 nodes.append(nodeid)
1613 request['nodes'] = nodes
James E. Blaira38c28e2017-01-04 10:33:20 -08001614
James E. Blaira38c28e2017-01-04 10:33:20 -08001615 request['state_time'] = time.time()
James E. Blairdce6cea2016-12-20 16:45:32 -08001616 path = self.REQUEST_ROOT + '/' + oid
Clint Byrumf322fe22017-05-10 20:53:12 -07001617 data = json.dumps(request).encode('utf8')
James E. Blairdce6cea2016-12-20 16:45:32 -08001618 self.log.debug("Fulfilling node request: %s %s" % (oid, data))
James E. Blaircbbce0d2017-05-19 07:28:29 -07001619 try:
1620 self.client.set(path, data)
1621 except kazoo.exceptions.NoNodeError:
1622 self.log.debug("Node request %s %s disappeared" % (oid, data))
James E. Blairdce6cea2016-12-20 16:45:32 -08001623
1624
James E. Blair498059b2016-12-20 13:50:13 -08001625class ChrootedKazooFixture(fixtures.Fixture):
Clark Boylan621ec9a2017-04-07 17:41:33 -07001626 def __init__(self, test_id):
James E. Blair498059b2016-12-20 13:50:13 -08001627 super(ChrootedKazooFixture, self).__init__()
1628
1629 zk_host = os.environ.get('NODEPOOL_ZK_HOST', 'localhost')
1630 if ':' in zk_host:
1631 host, port = zk_host.split(':')
1632 else:
1633 host = zk_host
1634 port = None
1635
1636 self.zookeeper_host = host
1637
1638 if not port:
1639 self.zookeeper_port = 2181
1640 else:
1641 self.zookeeper_port = int(port)
1642
Clark Boylan621ec9a2017-04-07 17:41:33 -07001643 self.test_id = test_id
1644
James E. Blair498059b2016-12-20 13:50:13 -08001645 def _setUp(self):
1646 # Make sure the test chroot paths do not conflict
1647 random_bits = ''.join(random.choice(string.ascii_lowercase +
1648 string.ascii_uppercase)
1649 for x in range(8))
1650
Clark Boylan621ec9a2017-04-07 17:41:33 -07001651 rand_test_path = '%s_%s_%s' % (random_bits, os.getpid(), self.test_id)
James E. Blair498059b2016-12-20 13:50:13 -08001652 self.zookeeper_chroot = "/nodepool_test/%s" % rand_test_path
1653
Clark Boylan7f5f1ec2017-04-07 17:39:56 -07001654 self.addCleanup(self._cleanup)
1655
James E. Blair498059b2016-12-20 13:50:13 -08001656 # Ensure the chroot path exists and clean up any pre-existing znodes.
1657 _tmp_client = kazoo.client.KazooClient(
1658 hosts='%s:%s' % (self.zookeeper_host, self.zookeeper_port))
1659 _tmp_client.start()
1660
1661 if _tmp_client.exists(self.zookeeper_chroot):
1662 _tmp_client.delete(self.zookeeper_chroot, recursive=True)
1663
1664 _tmp_client.ensure_path(self.zookeeper_chroot)
1665 _tmp_client.stop()
1666 _tmp_client.close()
1667
James E. Blair498059b2016-12-20 13:50:13 -08001668 def _cleanup(self):
1669 '''Remove the chroot path.'''
1670 # Need a non-chroot'ed client to remove the chroot path
1671 _tmp_client = kazoo.client.KazooClient(
1672 hosts='%s:%s' % (self.zookeeper_host, self.zookeeper_port))
1673 _tmp_client.start()
1674 _tmp_client.delete(self.zookeeper_chroot, recursive=True)
1675 _tmp_client.stop()
Clark Boylan7f5f1ec2017-04-07 17:39:56 -07001676 _tmp_client.close()
James E. Blair498059b2016-12-20 13:50:13 -08001677
1678
Joshua Heskethd78b4482015-09-14 16:56:34 -06001679class MySQLSchemaFixture(fixtures.Fixture):
1680 def setUp(self):
1681 super(MySQLSchemaFixture, self).setUp()
1682
1683 random_bits = ''.join(random.choice(string.ascii_lowercase +
1684 string.ascii_uppercase)
1685 for x in range(8))
1686 self.name = '%s_%s' % (random_bits, os.getpid())
1687 self.passwd = uuid.uuid4().hex
1688 db = pymysql.connect(host="localhost",
1689 user="openstack_citest",
1690 passwd="openstack_citest",
1691 db="openstack_citest")
1692 cur = db.cursor()
1693 cur.execute("create database %s" % self.name)
1694 cur.execute(
1695 "grant all on %s.* to '%s'@'localhost' identified by '%s'" %
1696 (self.name, self.name, self.passwd))
1697 cur.execute("flush privileges")
1698
1699 self.dburi = 'mysql+pymysql://%s:%s@localhost/%s' % (self.name,
1700 self.passwd,
1701 self.name)
1702 self.addDetail('dburi', testtools.content.text_content(self.dburi))
1703 self.addCleanup(self.cleanup)
1704
1705 def cleanup(self):
1706 db = pymysql.connect(host="localhost",
1707 user="openstack_citest",
1708 passwd="openstack_citest",
1709 db="openstack_citest")
1710 cur = db.cursor()
1711 cur.execute("drop database %s" % self.name)
1712 cur.execute("drop user '%s'@'localhost'" % self.name)
1713 cur.execute("flush privileges")
1714
1715
Maru Newby3fe5f852015-01-13 04:22:14 +00001716class BaseTestCase(testtools.TestCase):
Clark Boylanb640e052014-04-03 16:41:46 -07001717 log = logging.getLogger("zuul.test")
James E. Blair267e5162017-04-07 10:08:20 -07001718 wait_timeout = 30
Clark Boylanb640e052014-04-03 16:41:46 -07001719
James E. Blair1c236df2017-02-01 14:07:24 -08001720 def attachLogs(self, *args):
1721 def reader():
1722 self._log_stream.seek(0)
1723 while True:
1724 x = self._log_stream.read(4096)
1725 if not x:
1726 break
1727 yield x.encode('utf8')
1728 content = testtools.content.content_from_reader(
1729 reader,
1730 testtools.content_type.UTF8_TEXT,
1731 False)
1732 self.addDetail('logging', content)
1733
Clark Boylanb640e052014-04-03 16:41:46 -07001734 def setUp(self):
Maru Newby3fe5f852015-01-13 04:22:14 +00001735 super(BaseTestCase, self).setUp()
Clark Boylanb640e052014-04-03 16:41:46 -07001736 test_timeout = os.environ.get('OS_TEST_TIMEOUT', 0)
1737 try:
1738 test_timeout = int(test_timeout)
1739 except ValueError:
1740 # If timeout value is invalid do not set a timeout.
1741 test_timeout = 0
1742 if test_timeout > 0:
1743 self.useFixture(fixtures.Timeout(test_timeout, gentle=False))
1744
1745 if (os.environ.get('OS_STDOUT_CAPTURE') == 'True' or
1746 os.environ.get('OS_STDOUT_CAPTURE') == '1'):
1747 stdout = self.useFixture(fixtures.StringStream('stdout')).stream
1748 self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout))
1749 if (os.environ.get('OS_STDERR_CAPTURE') == 'True' or
1750 os.environ.get('OS_STDERR_CAPTURE') == '1'):
1751 stderr = self.useFixture(fixtures.StringStream('stderr')).stream
1752 self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr))
1753 if (os.environ.get('OS_LOG_CAPTURE') == 'True' or
1754 os.environ.get('OS_LOG_CAPTURE') == '1'):
James E. Blair1c236df2017-02-01 14:07:24 -08001755 self._log_stream = StringIO()
1756 self.addOnException(self.attachLogs)
1757 else:
1758 self._log_stream = sys.stdout
Maru Newby3fe5f852015-01-13 04:22:14 +00001759
James E. Blair73b41772017-05-22 13:22:55 -07001760 # NOTE(jeblair): this is temporary extra debugging to try to
1761 # track down a possible leak.
1762 orig_git_repo_init = git.Repo.__init__
1763
1764 def git_repo_init(myself, *args, **kw):
1765 orig_git_repo_init(myself, *args, **kw)
1766 self.log.debug("Created git repo 0x%x %s" %
1767 (id(myself), repr(myself)))
1768
1769 self.useFixture(fixtures.MonkeyPatch('git.Repo.__init__',
1770 git_repo_init))
1771
James E. Blair1c236df2017-02-01 14:07:24 -08001772 handler = logging.StreamHandler(self._log_stream)
1773 formatter = logging.Formatter('%(asctime)s %(name)-32s '
1774 '%(levelname)-8s %(message)s')
1775 handler.setFormatter(formatter)
1776
1777 logger = logging.getLogger()
1778 logger.setLevel(logging.DEBUG)
1779 logger.addHandler(handler)
1780
Clark Boylan3410d532017-04-25 12:35:29 -07001781 # Make sure we don't carry old handlers around in process state
1782 # which slows down test runs
1783 self.addCleanup(logger.removeHandler, handler)
1784 self.addCleanup(handler.close)
1785 self.addCleanup(handler.flush)
1786
James E. Blair1c236df2017-02-01 14:07:24 -08001787 # NOTE(notmorgan): Extract logging overrides for specific
1788 # libraries from the OS_LOG_DEFAULTS env and create loggers
1789 # for each. This is used to limit the output during test runs
1790 # from libraries that zuul depends on such as gear.
James E. Blairdce6cea2016-12-20 16:45:32 -08001791 log_defaults_from_env = os.environ.get(
1792 'OS_LOG_DEFAULTS',
Monty Taylor73db6492017-05-18 17:31:17 -05001793 'git.cmd=INFO,kazoo.client=WARNING,gear=INFO,paste=INFO')
Morgan Fainbergd34e0b42016-06-09 19:10:38 -07001794
James E. Blairdce6cea2016-12-20 16:45:32 -08001795 if log_defaults_from_env:
1796 for default in log_defaults_from_env.split(','):
1797 try:
1798 name, level_str = default.split('=', 1)
1799 level = getattr(logging, level_str, logging.DEBUG)
James E. Blair1c236df2017-02-01 14:07:24 -08001800 logger = logging.getLogger(name)
1801 logger.setLevel(level)
1802 logger.addHandler(handler)
1803 logger.propagate = False
James E. Blairdce6cea2016-12-20 16:45:32 -08001804 except ValueError:
1805 # NOTE(notmorgan): Invalid format of the log default,
1806 # skip and don't try and apply a logger for the
1807 # specified module
1808 pass
Morgan Fainbergd34e0b42016-06-09 19:10:38 -07001809
Maru Newby3fe5f852015-01-13 04:22:14 +00001810
1811class ZuulTestCase(BaseTestCase):
James E. Blaire7b99a02016-08-05 14:27:34 -07001812 """A test case with a functioning Zuul.
1813
1814 The following class variables are used during test setup and can
1815 be overidden by subclasses but are effectively read-only once a
1816 test method starts running:
1817
1818 :cvar str config_file: This points to the main zuul config file
1819 within the fixtures directory. Subclasses may override this
1820 to obtain a different behavior.
1821
1822 :cvar str tenant_config_file: This is the tenant config file
1823 (which specifies from what git repos the configuration should
1824 be loaded). It defaults to the value specified in
1825 `config_file` but can be overidden by subclasses to obtain a
1826 different tenant/project layout while using the standard main
James E. Blair06cc3922017-04-19 10:08:10 -07001827 configuration. See also the :py:func:`simple_layout`
1828 decorator.
James E. Blaire7b99a02016-08-05 14:27:34 -07001829
Ricardo Carrillo Cruz22994f92016-12-02 11:41:58 +00001830 :cvar bool create_project_keys: Indicates whether Zuul should
1831 auto-generate keys for each project, or whether the test
1832 infrastructure should insert dummy keys to save time during
1833 startup. Defaults to False.
1834
James E. Blaire7b99a02016-08-05 14:27:34 -07001835 The following are instance variables that are useful within test
1836 methods:
1837
1838 :ivar FakeGerritConnection fake_<connection>:
1839 A :py:class:`~tests.base.FakeGerritConnection` will be
1840 instantiated for each connection present in the config file
1841 and stored here. For instance, `fake_gerrit` will hold the
1842 FakeGerritConnection object for a connection named `gerrit`.
1843
1844 :ivar FakeGearmanServer gearman_server: An instance of
1845 :py:class:`~tests.base.FakeGearmanServer` which is the Gearman
1846 server that all of the Zuul components in this test use to
1847 communicate with each other.
1848
Paul Belanger174a8272017-03-14 13:20:10 -04001849 :ivar RecordingExecutorServer executor_server: An instance of
1850 :py:class:`~tests.base.RecordingExecutorServer` which is the
1851 Ansible execute server used to run jobs for this test.
James E. Blaire7b99a02016-08-05 14:27:34 -07001852
1853 :ivar list builds: A list of :py:class:`~tests.base.FakeBuild` objects
1854 representing currently running builds. They are appended to
Paul Belanger174a8272017-03-14 13:20:10 -04001855 the list in the order they are executed, and removed from this
James E. Blaire7b99a02016-08-05 14:27:34 -07001856 list upon completion.
1857
1858 :ivar list history: A list of :py:class:`~tests.base.BuildHistory`
1859 objects representing completed builds. They are appended to
1860 the list in the order they complete.
1861
1862 """
1863
James E. Blair83005782015-12-11 14:46:03 -08001864 config_file = 'zuul.conf'
James E. Blaire1767bc2016-08-02 10:00:27 -07001865 run_ansible = False
Ricardo Carrillo Cruz22994f92016-12-02 11:41:58 +00001866 create_project_keys = False
Paul Belanger0a21f0a2017-06-13 13:14:42 -04001867 use_ssl = False
James E. Blair3f876d52016-07-22 13:07:14 -07001868
1869 def _startMerger(self):
1870 self.merge_server = zuul.merger.server.MergeServer(self.config,
1871 self.connections)
1872 self.merge_server.start()
1873
Maru Newby3fe5f852015-01-13 04:22:14 +00001874 def setUp(self):
1875 super(ZuulTestCase, self).setUp()
James E. Blair498059b2016-12-20 13:50:13 -08001876
1877 self.setupZK()
1878
K Jonathan Harkercc3a6f02017-02-22 19:08:06 -08001879 if not KEEP_TEMPDIRS:
James E. Blair97d902e2014-08-21 13:25:56 -07001880 tmp_root = self.useFixture(fixtures.TempDir(
Joshua Hesketh29d99b72014-08-19 16:27:42 +10001881 rootdir=os.environ.get("ZUUL_TEST_ROOT"))
1882 ).path
James E. Blair97d902e2014-08-21 13:25:56 -07001883 else:
K Jonathan Harkercc3a6f02017-02-22 19:08:06 -08001884 tmp_root = tempfile.mkdtemp(
1885 dir=os.environ.get("ZUUL_TEST_ROOT", None))
Clark Boylanb640e052014-04-03 16:41:46 -07001886 self.test_root = os.path.join(tmp_root, "zuul-test")
1887 self.upstream_root = os.path.join(self.test_root, "upstream")
Monty Taylord642d852017-02-23 14:05:42 -05001888 self.merger_src_root = os.path.join(self.test_root, "merger-git")
Paul Belanger174a8272017-03-14 13:20:10 -04001889 self.executor_src_root = os.path.join(self.test_root, "executor-git")
James E. Blairce8a2132016-05-19 15:21:52 -07001890 self.state_root = os.path.join(self.test_root, "lib")
James E. Blair01d733e2017-06-23 20:47:51 +01001891 self.merger_state_root = os.path.join(self.test_root, "merger-lib")
1892 self.executor_state_root = os.path.join(self.test_root, "executor-lib")
Clark Boylanb640e052014-04-03 16:41:46 -07001893
1894 if os.path.exists(self.test_root):
1895 shutil.rmtree(self.test_root)
1896 os.makedirs(self.test_root)
1897 os.makedirs(self.upstream_root)
James E. Blairce8a2132016-05-19 15:21:52 -07001898 os.makedirs(self.state_root)
James E. Blair01d733e2017-06-23 20:47:51 +01001899 os.makedirs(self.merger_state_root)
1900 os.makedirs(self.executor_state_root)
Clark Boylanb640e052014-04-03 16:41:46 -07001901
1902 # Make per test copy of Configuration.
1903 self.setup_config()
Clint Byrum50c69d82017-05-04 11:55:20 -07001904 self.private_key_file = os.path.join(self.test_root, 'test_id_rsa')
1905 if not os.path.exists(self.private_key_file):
1906 src_private_key_file = os.path.join(FIXTURE_DIR, 'test_id_rsa')
1907 shutil.copy(src_private_key_file, self.private_key_file)
1908 shutil.copy('{}.pub'.format(src_private_key_file),
1909 '{}.pub'.format(self.private_key_file))
1910 os.chmod(self.private_key_file, 0o0600)
James E. Blair39840362017-06-23 20:34:02 +01001911 self.config.set('scheduler', 'tenant_config',
1912 os.path.join(
1913 FIXTURE_DIR,
1914 self.config.get('scheduler', 'tenant_config')))
James E. Blaird1de9462017-06-23 20:53:09 +01001915 self.config.set('scheduler', 'state_dir', self.state_root)
Monty Taylord642d852017-02-23 14:05:42 -05001916 self.config.set('merger', 'git_dir', self.merger_src_root)
James E. Blair01d733e2017-06-23 20:47:51 +01001917 self.config.set('merger', 'state_dir', self.merger_state_root)
Paul Belanger174a8272017-03-14 13:20:10 -04001918 self.config.set('executor', 'git_dir', self.executor_src_root)
Clint Byrum50c69d82017-05-04 11:55:20 -07001919 self.config.set('executor', 'private_key_file', self.private_key_file)
James E. Blair01d733e2017-06-23 20:47:51 +01001920 self.config.set('executor', 'state_dir', self.executor_state_root)
Clark Boylanb640e052014-04-03 16:41:46 -07001921
Clark Boylanb640e052014-04-03 16:41:46 -07001922 self.statsd = FakeStatsd()
Ian Wienandff977bf2015-09-30 15:38:47 +10001923 # note, use 127.0.0.1 rather than localhost to avoid getting ipv6
1924 # see: https://github.com/jsocol/pystatsd/issues/61
1925 os.environ['STATSD_HOST'] = '127.0.0.1'
Clark Boylanb640e052014-04-03 16:41:46 -07001926 os.environ['STATSD_PORT'] = str(self.statsd.port)
1927 self.statsd.start()
1928 # the statsd client object is configured in the statsd module import
Monty Taylorb934c1a2017-06-16 19:31:47 -05001929 importlib.reload(statsd)
1930 importlib.reload(zuul.scheduler)
Clark Boylanb640e052014-04-03 16:41:46 -07001931
Paul Belanger0a21f0a2017-06-13 13:14:42 -04001932 self.gearman_server = FakeGearmanServer(self.use_ssl)
Clark Boylanb640e052014-04-03 16:41:46 -07001933
1934 self.config.set('gearman', 'port', str(self.gearman_server.port))
James E. Blaire47eb772017-02-02 17:19:40 -08001935 self.log.info("Gearman server on port %s" %
1936 (self.gearman_server.port,))
Paul Belanger0a21f0a2017-06-13 13:14:42 -04001937 if self.use_ssl:
1938 self.log.info('SSL enabled for gearman')
1939 self.config.set(
1940 'gearman', 'ssl_ca',
1941 os.path.join(FIXTURE_DIR, 'gearman/root-ca.pem'))
1942 self.config.set(
1943 'gearman', 'ssl_cert',
1944 os.path.join(FIXTURE_DIR, 'gearman/client.pem'))
1945 self.config.set(
1946 'gearman', 'ssl_key',
1947 os.path.join(FIXTURE_DIR, 'gearman/client.key'))
Clark Boylanb640e052014-04-03 16:41:46 -07001948
James E. Blaire511d2f2016-12-08 15:22:26 -08001949 gerritsource.GerritSource.replication_timeout = 1.5
1950 gerritsource.GerritSource.replication_retry_interval = 0.5
1951 gerritconnection.GerritEventConnector.delay = 0.0
Clark Boylanb640e052014-04-03 16:41:46 -07001952
Joshua Hesketh352264b2015-08-11 23:42:08 +10001953 self.sched = zuul.scheduler.Scheduler(self.config)
Clark Boylanb640e052014-04-03 16:41:46 -07001954
Jan Hruban7083edd2015-08-21 14:00:54 +02001955 self.webapp = zuul.webapp.WebApp(
1956 self.sched, port=0, listen_address='127.0.0.1')
1957
Jan Hruban6b71aff2015-10-22 16:58:08 +02001958 self.event_queues = [
1959 self.sched.result_event_queue,
James E. Blair646322f2017-01-27 15:50:34 -08001960 self.sched.trigger_event_queue,
1961 self.sched.management_event_queue
Jan Hruban6b71aff2015-10-22 16:58:08 +02001962 ]
1963
James E. Blairfef78942016-03-11 16:28:56 -08001964 self.configure_connections()
Jan Hruban7083edd2015-08-21 14:00:54 +02001965 self.sched.registerConnections(self.connections, self.webapp)
Joshua Hesketh352264b2015-08-11 23:42:08 +10001966
Paul Belanger174a8272017-03-14 13:20:10 -04001967 self.executor_server = RecordingExecutorServer(
James E. Blaira92cbc82017-01-23 14:56:49 -08001968 self.config, self.connections,
James E. Blair854f8892017-02-02 11:25:39 -08001969 jobdir_root=self.test_root,
James E. Blaira92cbc82017-01-23 14:56:49 -08001970 _run_ansible=self.run_ansible,
K Jonathan Harkercc3a6f02017-02-22 19:08:06 -08001971 _test_root=self.test_root,
1972 keep_jobdir=KEEP_TEMPDIRS)
Paul Belanger174a8272017-03-14 13:20:10 -04001973 self.executor_server.start()
1974 self.history = self.executor_server.build_history
1975 self.builds = self.executor_server.running_builds
James E. Blaire1767bc2016-08-02 10:00:27 -07001976
Paul Belanger174a8272017-03-14 13:20:10 -04001977 self.executor_client = zuul.executor.client.ExecutorClient(
James E. Blair92e953a2017-03-07 13:08:47 -08001978 self.config, self.sched)
Joshua Hesketh850ccb62014-11-27 11:31:02 +11001979 self.merge_client = zuul.merger.client.MergeClient(
1980 self.config, self.sched)
James E. Blair8d692392016-04-08 17:47:58 -07001981 self.nodepool = zuul.nodepool.Nodepool(self.sched)
James E. Blairdce6cea2016-12-20 16:45:32 -08001982 self.zk = zuul.zk.ZooKeeper()
James E. Blair0d5a36e2017-02-21 10:53:44 -05001983 self.zk.connect(self.zk_config)
James E. Blairdce6cea2016-12-20 16:45:32 -08001984
James E. Blair0d5a36e2017-02-21 10:53:44 -05001985 self.fake_nodepool = FakeNodepool(
1986 self.zk_chroot_fixture.zookeeper_host,
1987 self.zk_chroot_fixture.zookeeper_port,
1988 self.zk_chroot_fixture.zookeeper_chroot)
Clark Boylanb640e052014-04-03 16:41:46 -07001989
Paul Belanger174a8272017-03-14 13:20:10 -04001990 self.sched.setExecutor(self.executor_client)
Clark Boylanb640e052014-04-03 16:41:46 -07001991 self.sched.setMerger(self.merge_client)
James E. Blair8d692392016-04-08 17:47:58 -07001992 self.sched.setNodepool(self.nodepool)
James E. Blairdce6cea2016-12-20 16:45:32 -08001993 self.sched.setZooKeeper(self.zk)
Clark Boylanb640e052014-04-03 16:41:46 -07001994
Joshua Hesketh850ccb62014-11-27 11:31:02 +11001995 self.rpc = zuul.rpclistener.RPCListener(self.config, self.sched)
Clark Boylanb640e052014-04-03 16:41:46 -07001996
1997 self.sched.start()
Clark Boylanb640e052014-04-03 16:41:46 -07001998 self.webapp.start()
1999 self.rpc.start()
Paul Belanger174a8272017-03-14 13:20:10 -04002000 self.executor_client.gearman.waitForServer()
James E. Blaira002b032017-04-18 10:35:48 -07002001 # Cleanups are run in reverse order
2002 self.addCleanup(self.assertCleanShutdown)
Clark Boylanb640e052014-04-03 16:41:46 -07002003 self.addCleanup(self.shutdown)
James E. Blaira002b032017-04-18 10:35:48 -07002004 self.addCleanup(self.assertFinalState)
Clark Boylanb640e052014-04-03 16:41:46 -07002005
James E. Blairb9c0d772017-03-03 14:34:49 -08002006 self.sched.reconfigure(self.config)
2007 self.sched.resume()
2008
Tobias Henkel7df274b2017-05-26 17:41:11 +02002009 def configure_connections(self, source_only=False):
James E. Blaire511d2f2016-12-08 15:22:26 -08002010 # Set up gerrit related fakes
2011 # Set a changes database so multiple FakeGerrit's can report back to
2012 # a virtual canonical database given by the configured hostname
2013 self.gerrit_changes_dbs = {}
2014
2015 def getGerritConnection(driver, name, config):
2016 db = self.gerrit_changes_dbs.setdefault(config['server'], {})
2017 con = FakeGerritConnection(driver, name, config,
2018 changes_db=db,
2019 upstream_root=self.upstream_root)
2020 self.event_queues.append(con.event_queue)
2021 setattr(self, 'fake_' + name, con)
2022 return con
2023
2024 self.useFixture(fixtures.MonkeyPatch(
2025 'zuul.driver.gerrit.GerritDriver.getConnection',
2026 getGerritConnection))
2027
Gregory Haynes4fc12542015-04-22 20:38:06 -07002028 def getGithubConnection(driver, name, config):
2029 con = FakeGithubConnection(driver, name, config,
2030 upstream_root=self.upstream_root)
2031 setattr(self, 'fake_' + name, con)
2032 return con
2033
2034 self.useFixture(fixtures.MonkeyPatch(
2035 'zuul.driver.github.GithubDriver.getConnection',
2036 getGithubConnection))
2037
James E. Blaire511d2f2016-12-08 15:22:26 -08002038 # Set up smtp related fakes
Joshua Heskethd78b4482015-09-14 16:56:34 -06002039 # TODO(jhesketh): This should come from lib.connections for better
2040 # coverage
Joshua Hesketh352264b2015-08-11 23:42:08 +10002041 # Register connections from the config
2042 self.smtp_messages = []
Joshua Hesketh850ccb62014-11-27 11:31:02 +11002043
Joshua Hesketh352264b2015-08-11 23:42:08 +10002044 def FakeSMTPFactory(*args, **kw):
2045 args = [self.smtp_messages] + list(args)
2046 return FakeSMTP(*args, **kw)
Joshua Hesketh850ccb62014-11-27 11:31:02 +11002047
Joshua Hesketh352264b2015-08-11 23:42:08 +10002048 self.useFixture(fixtures.MonkeyPatch('smtplib.SMTP', FakeSMTPFactory))
Joshua Hesketh850ccb62014-11-27 11:31:02 +11002049
James E. Blaire511d2f2016-12-08 15:22:26 -08002050 # Register connections from the config using fakes
James E. Blairfef78942016-03-11 16:28:56 -08002051 self.connections = zuul.lib.connections.ConnectionRegistry()
Tobias Henkel7df274b2017-05-26 17:41:11 +02002052 self.connections.configure(self.config, source_only=source_only)
Joshua Hesketh850ccb62014-11-27 11:31:02 +11002053
James E. Blair83005782015-12-11 14:46:03 -08002054 def setup_config(self):
James E. Blaire7b99a02016-08-05 14:27:34 -07002055 # This creates the per-test configuration object. It can be
2056 # overriden by subclasses, but should not need to be since it
2057 # obeys the config_file and tenant_config_file attributes.
Monty Taylorb934c1a2017-06-16 19:31:47 -05002058 self.config = configparser.ConfigParser()
James E. Blair83005782015-12-11 14:46:03 -08002059 self.config.read(os.path.join(FIXTURE_DIR, self.config_file))
James E. Blair06cc3922017-04-19 10:08:10 -07002060
James E. Blair39840362017-06-23 20:34:02 +01002061 sections = ['zuul', 'scheduler', 'executor', 'merger']
2062 for section in sections:
2063 if not self.config.has_section(section):
2064 self.config.add_section(section)
2065
James E. Blair06cc3922017-04-19 10:08:10 -07002066 if not self.setupSimpleLayout():
2067 if hasattr(self, 'tenant_config_file'):
James E. Blair39840362017-06-23 20:34:02 +01002068 self.config.set('scheduler', 'tenant_config',
James E. Blair06cc3922017-04-19 10:08:10 -07002069 self.tenant_config_file)
2070 git_path = os.path.join(
2071 os.path.dirname(
2072 os.path.join(FIXTURE_DIR, self.tenant_config_file)),
2073 'git')
2074 if os.path.exists(git_path):
2075 for reponame in os.listdir(git_path):
2076 project = reponame.replace('_', '/')
2077 self.copyDirToRepo(project,
2078 os.path.join(git_path, reponame))
Tristan Cacqueray44aef152017-06-15 06:00:12 +00002079 # Make test_root persist after ansible run for .flag test
2080 self.config.set('executor', 'trusted_rw_dirs', self.test_root)
Ricardo Carrillo Cruz22994f92016-12-02 11:41:58 +00002081 self.setupAllProjectKeys()
2082
James E. Blair06cc3922017-04-19 10:08:10 -07002083 def setupSimpleLayout(self):
2084 # If the test method has been decorated with a simple_layout,
2085 # use that instead of the class tenant_config_file. Set up a
2086 # single config-project with the specified layout, and
2087 # initialize repos for all of the 'project' entries which
2088 # appear in the layout.
2089 test_name = self.id().split('.')[-1]
2090 test = getattr(self, test_name)
2091 if hasattr(test, '__simple_layout__'):
Jesse Keating436a5452017-04-20 11:48:41 -07002092 path, driver = getattr(test, '__simple_layout__')
James E. Blair06cc3922017-04-19 10:08:10 -07002093 else:
2094 return False
2095
James E. Blairb70e55a2017-04-19 12:57:02 -07002096 files = {}
James E. Blair06cc3922017-04-19 10:08:10 -07002097 path = os.path.join(FIXTURE_DIR, path)
2098 with open(path) as f:
James E. Blairb70e55a2017-04-19 12:57:02 -07002099 data = f.read()
2100 layout = yaml.safe_load(data)
2101 files['zuul.yaml'] = data
James E. Blair06cc3922017-04-19 10:08:10 -07002102 untrusted_projects = []
2103 for item in layout:
2104 if 'project' in item:
2105 name = item['project']['name']
2106 untrusted_projects.append(name)
2107 self.init_repo(name)
2108 self.addCommitToRepo(name, 'initial commit',
2109 files={'README': ''},
2110 branch='master', tag='init')
James E. Blairb70e55a2017-04-19 12:57:02 -07002111 if 'job' in item:
2112 jobname = item['job']['name']
2113 files['playbooks/%s.yaml' % jobname] = ''
James E. Blair06cc3922017-04-19 10:08:10 -07002114
2115 root = os.path.join(self.test_root, "config")
2116 if not os.path.exists(root):
2117 os.makedirs(root)
2118 f = tempfile.NamedTemporaryFile(dir=root, delete=False)
2119 config = [{'tenant':
2120 {'name': 'tenant-one',
Jesse Keating436a5452017-04-20 11:48:41 -07002121 'source': {driver:
James E. Blair06cc3922017-04-19 10:08:10 -07002122 {'config-projects': ['common-config'],
2123 'untrusted-projects': untrusted_projects}}}}]
Clint Byrumd52b7d72017-05-10 19:40:35 -07002124 f.write(yaml.dump(config).encode('utf8'))
James E. Blair06cc3922017-04-19 10:08:10 -07002125 f.close()
James E. Blair39840362017-06-23 20:34:02 +01002126 self.config.set('scheduler', 'tenant_config',
James E. Blair06cc3922017-04-19 10:08:10 -07002127 os.path.join(FIXTURE_DIR, f.name))
2128
2129 self.init_repo('common-config')
James E. Blair06cc3922017-04-19 10:08:10 -07002130 self.addCommitToRepo('common-config', 'add content from fixture',
2131 files, branch='master', tag='init')
2132
2133 return True
2134
Ricardo Carrillo Cruz22994f92016-12-02 11:41:58 +00002135 def setupAllProjectKeys(self):
2136 if self.create_project_keys:
2137 return
2138
James E. Blair39840362017-06-23 20:34:02 +01002139 path = self.config.get('scheduler', 'tenant_config')
Ricardo Carrillo Cruz22994f92016-12-02 11:41:58 +00002140 with open(os.path.join(FIXTURE_DIR, path)) as f:
2141 tenant_config = yaml.safe_load(f.read())
2142 for tenant in tenant_config:
2143 sources = tenant['tenant']['source']
2144 for source, conf in sources.items():
James E. Blair109da3f2017-04-04 14:39:43 -07002145 for project in conf.get('config-projects', []):
Ricardo Carrillo Cruz22994f92016-12-02 11:41:58 +00002146 self.setupProjectKeys(source, project)
James E. Blair109da3f2017-04-04 14:39:43 -07002147 for project in conf.get('untrusted-projects', []):
Ricardo Carrillo Cruz22994f92016-12-02 11:41:58 +00002148 self.setupProjectKeys(source, project)
2149
2150 def setupProjectKeys(self, source, project):
2151 # Make sure we set up an RSA key for the project so that we
2152 # don't spend time generating one:
2153
James E. Blair6459db12017-06-29 14:57:20 -07002154 if isinstance(project, dict):
2155 project = list(project.keys())[0]
Ricardo Carrillo Cruz22994f92016-12-02 11:41:58 +00002156 key_root = os.path.join(self.state_root, 'keys')
2157 if not os.path.isdir(key_root):
2158 os.mkdir(key_root, 0o700)
2159 private_key_file = os.path.join(key_root, source, project + '.pem')
2160 private_key_dir = os.path.dirname(private_key_file)
2161 self.log.debug("Installing test keys for project %s at %s" % (
2162 project, private_key_file))
2163 if not os.path.isdir(private_key_dir):
2164 os.makedirs(private_key_dir)
2165 with open(os.path.join(FIXTURE_DIR, 'private.pem')) as i:
2166 with open(private_key_file, 'w') as o:
2167 o.write(i.read())
James E. Blair96c6bf82016-01-15 16:20:40 -08002168
James E. Blair498059b2016-12-20 13:50:13 -08002169 def setupZK(self):
Clark Boylan621ec9a2017-04-07 17:41:33 -07002170 self.zk_chroot_fixture = self.useFixture(
2171 ChrootedKazooFixture(self.id()))
James E. Blair0d5a36e2017-02-21 10:53:44 -05002172 self.zk_config = '%s:%s%s' % (
James E. Blairdce6cea2016-12-20 16:45:32 -08002173 self.zk_chroot_fixture.zookeeper_host,
2174 self.zk_chroot_fixture.zookeeper_port,
2175 self.zk_chroot_fixture.zookeeper_chroot)
James E. Blair498059b2016-12-20 13:50:13 -08002176
James E. Blair96c6bf82016-01-15 16:20:40 -08002177 def copyDirToRepo(self, project, source_path):
James E. Blair8b1dc3f2016-07-05 16:49:00 -07002178 self.init_repo(project)
James E. Blair96c6bf82016-01-15 16:20:40 -08002179
2180 files = {}
2181 for (dirpath, dirnames, filenames) in os.walk(source_path):
2182 for filename in filenames:
2183 test_tree_filepath = os.path.join(dirpath, filename)
2184 common_path = os.path.commonprefix([test_tree_filepath,
2185 source_path])
2186 relative_filepath = test_tree_filepath[len(common_path) + 1:]
2187 with open(test_tree_filepath, 'r') as f:
2188 content = f.read()
2189 files[relative_filepath] = content
2190 self.addCommitToRepo(project, 'add content from fixture',
James E. Blair8b1dc3f2016-07-05 16:49:00 -07002191 files, branch='master', tag='init')
James E. Blair83005782015-12-11 14:46:03 -08002192
James E. Blaire18d4602017-01-05 11:17:28 -08002193 def assertNodepoolState(self):
2194 # Make sure that there are no pending requests
2195
2196 requests = self.fake_nodepool.getNodeRequests()
2197 self.assertEqual(len(requests), 0)
2198
2199 nodes = self.fake_nodepool.getNodes()
2200 for node in nodes:
2201 self.assertFalse(node['_lock'], "Node %s is locked" %
2202 (node['_oid'],))
2203
Ricardo Carrillo Cruz22994f92016-12-02 11:41:58 +00002204 def assertNoGeneratedKeys(self):
2205 # Make sure that Zuul did not generate any project keys
2206 # (unless it was supposed to).
2207
2208 if self.create_project_keys:
2209 return
2210
2211 with open(os.path.join(FIXTURE_DIR, 'private.pem')) as i:
2212 test_key = i.read()
2213
2214 key_root = os.path.join(self.state_root, 'keys')
2215 for root, dirname, files in os.walk(key_root):
2216 for fn in files:
2217 with open(os.path.join(root, fn)) as f:
2218 self.assertEqual(test_key, f.read())
2219
Clark Boylanb640e052014-04-03 16:41:46 -07002220 def assertFinalState(self):
James E. Blaira002b032017-04-18 10:35:48 -07002221 self.log.debug("Assert final state")
2222 # Make sure no jobs are running
2223 self.assertEqual({}, self.executor_server.job_workers)
Clark Boylanb640e052014-04-03 16:41:46 -07002224 # Make sure that git.Repo objects have been garbage collected.
James E. Blair73b41772017-05-22 13:22:55 -07002225 gc.disable()
Clark Boylanb640e052014-04-03 16:41:46 -07002226 gc.collect()
2227 for obj in gc.get_objects():
2228 if isinstance(obj, git.Repo):
James E. Blair73b41772017-05-22 13:22:55 -07002229 self.log.debug("Leaked git repo object: 0x%x %s" %
2230 (id(obj), repr(obj)))
James E. Blair73b41772017-05-22 13:22:55 -07002231 gc.enable()
Clark Boylanb640e052014-04-03 16:41:46 -07002232 self.assertEmptyQueues()
James E. Blaire18d4602017-01-05 11:17:28 -08002233 self.assertNodepoolState()
Ricardo Carrillo Cruz22994f92016-12-02 11:41:58 +00002234 self.assertNoGeneratedKeys()
James E. Blair83005782015-12-11 14:46:03 -08002235 ipm = zuul.manager.independent.IndependentPipelineManager
James E. Blair59fdbac2015-12-07 17:08:06 -08002236 for tenant in self.sched.abide.tenants.values():
2237 for pipeline in tenant.layout.pipelines.values():
James E. Blair83005782015-12-11 14:46:03 -08002238 if isinstance(pipeline.manager, ipm):
James E. Blair59fdbac2015-12-07 17:08:06 -08002239 self.assertEqual(len(pipeline.queues), 0)
Clark Boylanb640e052014-04-03 16:41:46 -07002240
2241 def shutdown(self):
2242 self.log.debug("Shutting down after tests")
James E. Blair5426b112017-05-26 14:19:54 -07002243 self.executor_server.hold_jobs_in_build = False
2244 self.executor_server.release()
Paul Belanger174a8272017-03-14 13:20:10 -04002245 self.executor_client.stop()
Clark Boylanb640e052014-04-03 16:41:46 -07002246 self.merge_client.stop()
Paul Belanger174a8272017-03-14 13:20:10 -04002247 self.executor_server.stop()
Clark Boylanb640e052014-04-03 16:41:46 -07002248 self.sched.stop()
2249 self.sched.join()
2250 self.statsd.stop()
2251 self.statsd.join()
2252 self.webapp.stop()
2253 self.webapp.join()
2254 self.rpc.stop()
2255 self.rpc.join()
2256 self.gearman_server.shutdown()
James E. Blairdce6cea2016-12-20 16:45:32 -08002257 self.fake_nodepool.stop()
2258 self.zk.disconnect()
Clark Boylan8208c192017-04-24 18:08:08 -07002259 self.printHistory()
Tobias Henkel9b546cd2017-05-16 09:48:03 +02002260 # We whitelist watchdog threads as they have relatively long delays
Clark Boylanf18e3b82017-04-24 17:34:13 -07002261 # before noticing they should exit, but they should exit on their own.
Tobias Henkel9b546cd2017-05-16 09:48:03 +02002262 # Further the pydevd threads also need to be whitelisted so debugging
2263 # e.g. in PyCharm is possible without breaking shutdown.
2264 whitelist = ['executor-watchdog',
2265 'pydevd.CommandThread',
2266 'pydevd.Reader',
2267 'pydevd.Writer',
2268 ]
Clark Boylanf18e3b82017-04-24 17:34:13 -07002269 threads = [t for t in threading.enumerate()
Tobias Henkel9b546cd2017-05-16 09:48:03 +02002270 if t.name not in whitelist]
Clark Boylanb640e052014-04-03 16:41:46 -07002271 if len(threads) > 1:
Clark Boylan8208c192017-04-24 18:08:08 -07002272 log_str = ""
2273 for thread_id, stack_frame in sys._current_frames().items():
2274 log_str += "Thread: %s\n" % thread_id
2275 log_str += "".join(traceback.format_stack(stack_frame))
2276 self.log.debug(log_str)
2277 raise Exception("More than one thread is running: %s" % threads)
Clark Boylanb640e052014-04-03 16:41:46 -07002278
James E. Blaira002b032017-04-18 10:35:48 -07002279 def assertCleanShutdown(self):
2280 pass
2281
James E. Blairc4ba97a2017-04-19 16:26:24 -07002282 def init_repo(self, project, tag=None):
Clark Boylanb640e052014-04-03 16:41:46 -07002283 parts = project.split('/')
2284 path = os.path.join(self.upstream_root, *parts[:-1])
2285 if not os.path.exists(path):
2286 os.makedirs(path)
2287 path = os.path.join(self.upstream_root, project)
2288 repo = git.Repo.init(path)
2289
Morgan Fainberg78c301a2016-07-14 13:47:01 -07002290 with repo.config_writer() as config_writer:
2291 config_writer.set_value('user', 'email', 'user@example.com')
2292 config_writer.set_value('user', 'name', 'User Name')
Clark Boylanb640e052014-04-03 16:41:46 -07002293
Clark Boylanb640e052014-04-03 16:41:46 -07002294 repo.index.commit('initial commit')
2295 master = repo.create_head('master')
James E. Blairc4ba97a2017-04-19 16:26:24 -07002296 if tag:
2297 repo.create_tag(tag)
Clark Boylanb640e052014-04-03 16:41:46 -07002298
James E. Blair97d902e2014-08-21 13:25:56 -07002299 repo.head.reference = master
James E. Blair879dafb2015-07-17 14:04:49 -07002300 zuul.merger.merger.reset_repo_to_head(repo)
James E. Blair97d902e2014-08-21 13:25:56 -07002301 repo.git.clean('-x', '-f', '-d')
2302
James E. Blair97d902e2014-08-21 13:25:56 -07002303 def create_branch(self, project, branch):
2304 path = os.path.join(self.upstream_root, project)
2305 repo = git.Repo.init(path)
2306 fn = os.path.join(path, 'README')
2307
2308 branch_head = repo.create_head(branch)
2309 repo.head.reference = branch_head
Clark Boylanb640e052014-04-03 16:41:46 -07002310 f = open(fn, 'a')
James E. Blair97d902e2014-08-21 13:25:56 -07002311 f.write("test %s\n" % branch)
Clark Boylanb640e052014-04-03 16:41:46 -07002312 f.close()
2313 repo.index.add([fn])
James E. Blair97d902e2014-08-21 13:25:56 -07002314 repo.index.commit('%s commit' % branch)
Clark Boylanb640e052014-04-03 16:41:46 -07002315
James E. Blair97d902e2014-08-21 13:25:56 -07002316 repo.head.reference = repo.heads['master']
James E. Blair879dafb2015-07-17 14:04:49 -07002317 zuul.merger.merger.reset_repo_to_head(repo)
Clark Boylanb640e052014-04-03 16:41:46 -07002318 repo.git.clean('-x', '-f', '-d')
2319
Sachi King9f16d522016-03-16 12:20:45 +11002320 def create_commit(self, project):
2321 path = os.path.join(self.upstream_root, project)
2322 repo = git.Repo(path)
2323 repo.head.reference = repo.heads['master']
2324 file_name = os.path.join(path, 'README')
2325 with open(file_name, 'a') as f:
2326 f.write('creating fake commit\n')
2327 repo.index.add([file_name])
2328 commit = repo.index.commit('Creating a fake commit')
2329 return commit.hexsha
2330
James E. Blairf4a5f022017-04-18 14:01:10 -07002331 def orderedRelease(self, count=None):
James E. Blairb8c16472015-05-05 14:55:26 -07002332 # Run one build at a time to ensure non-race order:
James E. Blairf4a5f022017-04-18 14:01:10 -07002333 i = 0
James E. Blairb8c16472015-05-05 14:55:26 -07002334 while len(self.builds):
2335 self.release(self.builds[0])
2336 self.waitUntilSettled()
James E. Blairf4a5f022017-04-18 14:01:10 -07002337 i += 1
2338 if count is not None and i >= count:
2339 break
James E. Blairb8c16472015-05-05 14:55:26 -07002340
James E. Blairdf25ddc2017-07-08 07:57:09 -07002341 def getSortedBuilds(self):
2342 "Return the list of currently running builds sorted by name"
2343
2344 return sorted(self.builds, key=lambda x: x.name)
2345
Clark Boylanb640e052014-04-03 16:41:46 -07002346 def release(self, job):
2347 if isinstance(job, FakeBuild):
2348 job.release()
2349 else:
2350 job.waiting = False
2351 self.log.debug("Queued job %s released" % job.unique)
2352 self.gearman_server.wakeConnections()
2353
2354 def getParameter(self, job, name):
2355 if isinstance(job, FakeBuild):
2356 return job.parameters[name]
2357 else:
2358 parameters = json.loads(job.arguments)
2359 return parameters[name]
2360
Clark Boylanb640e052014-04-03 16:41:46 -07002361 def haveAllBuildsReported(self):
2362 # See if Zuul is waiting on a meta job to complete
Paul Belanger174a8272017-03-14 13:20:10 -04002363 if self.executor_client.meta_jobs:
Clark Boylanb640e052014-04-03 16:41:46 -07002364 return False
2365 # Find out if every build that the worker has completed has been
2366 # reported back to Zuul. If it hasn't then that means a Gearman
2367 # event is still in transit and the system is not stable.
James E. Blair3f876d52016-07-22 13:07:14 -07002368 for build in self.history:
Paul Belanger174a8272017-03-14 13:20:10 -04002369 zbuild = self.executor_client.builds.get(build.uuid)
Clark Boylanb640e052014-04-03 16:41:46 -07002370 if not zbuild:
2371 # It has already been reported
2372 continue
2373 # It hasn't been reported yet.
2374 return False
2375 # Make sure that none of the worker connections are in GRAB_WAIT
James E. Blair24c07032017-06-02 15:26:35 -07002376 worker = self.executor_server.executor_worker
2377 for connection in worker.active_connections:
Clark Boylanb640e052014-04-03 16:41:46 -07002378 if connection.state == 'GRAB_WAIT':
2379 return False
2380 return True
2381
2382 def areAllBuildsWaiting(self):
Paul Belanger174a8272017-03-14 13:20:10 -04002383 builds = self.executor_client.builds.values()
James E. Blaira002b032017-04-18 10:35:48 -07002384 seen_builds = set()
Clark Boylanb640e052014-04-03 16:41:46 -07002385 for build in builds:
James E. Blaira002b032017-04-18 10:35:48 -07002386 seen_builds.add(build.uuid)
Clark Boylanb640e052014-04-03 16:41:46 -07002387 client_job = None
Paul Belanger174a8272017-03-14 13:20:10 -04002388 for conn in self.executor_client.gearman.active_connections:
Clark Boylanb640e052014-04-03 16:41:46 -07002389 for j in conn.related_jobs.values():
2390 if j.unique == build.uuid:
2391 client_job = j
2392 break
2393 if not client_job:
2394 self.log.debug("%s is not known to the gearman client" %
2395 build)
James E. Blairf15139b2015-04-02 16:37:15 -07002396 return False
Clark Boylanb640e052014-04-03 16:41:46 -07002397 if not client_job.handle:
2398 self.log.debug("%s has no handle" % client_job)
James E. Blairf15139b2015-04-02 16:37:15 -07002399 return False
Clark Boylanb640e052014-04-03 16:41:46 -07002400 server_job = self.gearman_server.jobs.get(client_job.handle)
2401 if not server_job:
2402 self.log.debug("%s is not known to the gearman server" %
2403 client_job)
James E. Blairf15139b2015-04-02 16:37:15 -07002404 return False
Clark Boylanb640e052014-04-03 16:41:46 -07002405 if not hasattr(server_job, 'waiting'):
2406 self.log.debug("%s is being enqueued" % server_job)
James E. Blairf15139b2015-04-02 16:37:15 -07002407 return False
Clark Boylanb640e052014-04-03 16:41:46 -07002408 if server_job.waiting:
2409 continue
James E. Blair17302972016-08-10 16:11:42 -07002410 if build.url is None:
James E. Blairbbda4702016-03-09 15:19:56 -08002411 self.log.debug("%s has not reported start" % build)
2412 return False
Clint Byrumf322fe22017-05-10 20:53:12 -07002413 # using internal ServerJob which offers no Text interface
Paul Belanger174a8272017-03-14 13:20:10 -04002414 worker_build = self.executor_server.job_builds.get(
Clint Byrumf322fe22017-05-10 20:53:12 -07002415 server_job.unique.decode('utf8'))
James E. Blair962220f2016-08-03 11:22:38 -07002416 if worker_build:
2417 if worker_build.isWaiting():
2418 continue
2419 else:
2420 self.log.debug("%s is running" % worker_build)
2421 return False
Clark Boylanb640e052014-04-03 16:41:46 -07002422 else:
James E. Blair962220f2016-08-03 11:22:38 -07002423 self.log.debug("%s is unassigned" % server_job)
James E. Blairf15139b2015-04-02 16:37:15 -07002424 return False
James E. Blaira002b032017-04-18 10:35:48 -07002425 for (build_uuid, job_worker) in \
2426 self.executor_server.job_workers.items():
2427 if build_uuid not in seen_builds:
2428 self.log.debug("%s is not finalized" % build_uuid)
2429 return False
James E. Blairf15139b2015-04-02 16:37:15 -07002430 return True
Clark Boylanb640e052014-04-03 16:41:46 -07002431
James E. Blairdce6cea2016-12-20 16:45:32 -08002432 def areAllNodeRequestsComplete(self):
James E. Blair15be0e12017-01-03 13:45:20 -08002433 if self.fake_nodepool.paused:
2434 return True
James E. Blairdce6cea2016-12-20 16:45:32 -08002435 if self.sched.nodepool.requests:
2436 return False
2437 return True
2438
Jan Hruban6b71aff2015-10-22 16:58:08 +02002439 def eventQueuesEmpty(self):
Monty Taylorb934c1a2017-06-16 19:31:47 -05002440 for event_queue in self.event_queues:
2441 yield event_queue.empty()
Jan Hruban6b71aff2015-10-22 16:58:08 +02002442
2443 def eventQueuesJoin(self):
Monty Taylorb934c1a2017-06-16 19:31:47 -05002444 for event_queue in self.event_queues:
2445 event_queue.join()
Jan Hruban6b71aff2015-10-22 16:58:08 +02002446
Clark Boylanb640e052014-04-03 16:41:46 -07002447 def waitUntilSettled(self):
2448 self.log.debug("Waiting until settled...")
2449 start = time.time()
2450 while True:
Clint Byruma9626572017-02-22 14:04:00 -05002451 if time.time() - start > self.wait_timeout:
James E. Blair10fc1eb2016-12-21 16:16:25 -08002452 self.log.error("Timeout waiting for Zuul to settle")
2453 self.log.error("Queue status:")
Monty Taylorb934c1a2017-06-16 19:31:47 -05002454 for event_queue in self.event_queues:
2455 self.log.error(" %s: %s" %
2456 (event_queue, event_queue.empty()))
James E. Blair10fc1eb2016-12-21 16:16:25 -08002457 self.log.error("All builds waiting: %s" %
James E. Blair622c9682016-06-09 08:14:53 -07002458 (self.areAllBuildsWaiting(),))
James E. Blair10fc1eb2016-12-21 16:16:25 -08002459 self.log.error("All builds reported: %s" %
James E. Blairf3156c92016-08-10 15:32:19 -07002460 (self.haveAllBuildsReported(),))
James E. Blair10fc1eb2016-12-21 16:16:25 -08002461 self.log.error("All requests completed: %s" %
2462 (self.areAllNodeRequestsComplete(),))
2463 self.log.error("Merge client jobs: %s" %
2464 (self.merge_client.jobs,))
Clark Boylanb640e052014-04-03 16:41:46 -07002465 raise Exception("Timeout waiting for Zuul to settle")
2466 # Make sure no new events show up while we're checking
James E. Blair3f876d52016-07-22 13:07:14 -07002467
Paul Belanger174a8272017-03-14 13:20:10 -04002468 self.executor_server.lock.acquire()
Clark Boylanb640e052014-04-03 16:41:46 -07002469 # have all build states propogated to zuul?
2470 if self.haveAllBuildsReported():
2471 # Join ensures that the queue is empty _and_ events have been
2472 # processed
Jan Hruban6b71aff2015-10-22 16:58:08 +02002473 self.eventQueuesJoin()
Clark Boylanb640e052014-04-03 16:41:46 -07002474 self.sched.run_handler_lock.acquire()
James E. Blair14abdf42015-12-09 16:11:53 -08002475 if (not self.merge_client.jobs and
Clark Boylanb640e052014-04-03 16:41:46 -07002476 self.haveAllBuildsReported() and
James E. Blairdce6cea2016-12-20 16:45:32 -08002477 self.areAllBuildsWaiting() and
James E. Blair36c611a2017-02-06 15:59:43 -08002478 self.areAllNodeRequestsComplete() and
2479 all(self.eventQueuesEmpty())):
2480 # The queue empty check is placed at the end to
2481 # ensure that if a component adds an event between
2482 # when locked the run handler and checked that the
2483 # components were stable, we don't erroneously
2484 # report that we are settled.
Clark Boylanb640e052014-04-03 16:41:46 -07002485 self.sched.run_handler_lock.release()
Paul Belanger174a8272017-03-14 13:20:10 -04002486 self.executor_server.lock.release()
Clark Boylanb640e052014-04-03 16:41:46 -07002487 self.log.debug("...settled.")
2488 return
2489 self.sched.run_handler_lock.release()
Paul Belanger174a8272017-03-14 13:20:10 -04002490 self.executor_server.lock.release()
Clark Boylanb640e052014-04-03 16:41:46 -07002491 self.sched.wake_event.wait(0.1)
2492
2493 def countJobResults(self, jobs, result):
2494 jobs = filter(lambda x: x.result == result, jobs)
Clint Byrum1d0c7d12017-05-10 19:40:53 -07002495 return len(list(jobs))
Clark Boylanb640e052014-04-03 16:41:46 -07002496
Monty Taylor0d926122017-05-24 08:07:56 -05002497 def getBuildByName(self, name):
2498 for build in self.builds:
2499 if build.name == name:
2500 return build
2501 raise Exception("Unable to find build %s" % name)
2502
James E. Blair96c6bf82016-01-15 16:20:40 -08002503 def getJobFromHistory(self, name, project=None):
James E. Blair3f876d52016-07-22 13:07:14 -07002504 for job in self.history:
2505 if (job.name == name and
2506 (project is None or
James E. Blaire5366092017-07-21 15:30:39 -07002507 job.parameters['zuul']['project']['name'] == project)):
James E. Blair3f876d52016-07-22 13:07:14 -07002508 return job
Clark Boylanb640e052014-04-03 16:41:46 -07002509 raise Exception("Unable to find job %s in history" % name)
2510
2511 def assertEmptyQueues(self):
2512 # Make sure there are no orphaned jobs
James E. Blair59fdbac2015-12-07 17:08:06 -08002513 for tenant in self.sched.abide.tenants.values():
2514 for pipeline in tenant.layout.pipelines.values():
Monty Taylorb934c1a2017-06-16 19:31:47 -05002515 for pipeline_queue in pipeline.queues:
2516 if len(pipeline_queue.queue) != 0:
Joshua Hesketh0aa7e8b2016-07-14 00:12:25 +10002517 print('pipeline %s queue %s contents %s' % (
Monty Taylorb934c1a2017-06-16 19:31:47 -05002518 pipeline.name, pipeline_queue.name,
2519 pipeline_queue.queue))
2520 self.assertEqual(len(pipeline_queue.queue), 0,
James E. Blair59fdbac2015-12-07 17:08:06 -08002521 "Pipelines queues should be empty")
Clark Boylanb640e052014-04-03 16:41:46 -07002522
2523 def assertReportedStat(self, key, value=None, kind=None):
2524 start = time.time()
2525 while time.time() < (start + 5):
2526 for stat in self.statsd.stats:
Clint Byrumf322fe22017-05-10 20:53:12 -07002527 k, v = stat.decode('utf-8').split(':')
Clark Boylanb640e052014-04-03 16:41:46 -07002528 if key == k:
2529 if value is None and kind is None:
2530 return
2531 elif value:
2532 if value == v:
2533 return
2534 elif kind:
2535 if v.endswith('|' + kind):
2536 return
2537 time.sleep(0.1)
2538
Clark Boylanb640e052014-04-03 16:41:46 -07002539 raise Exception("Key %s not found in reported stats" % key)
James E. Blair59fdbac2015-12-07 17:08:06 -08002540
James E. Blair2b2a8ab2016-08-11 14:39:11 -07002541 def assertBuilds(self, builds):
2542 """Assert that the running builds are as described.
2543
2544 The list of running builds is examined and must match exactly
2545 the list of builds described by the input.
2546
2547 :arg list builds: A list of dictionaries. Each item in the
2548 list must match the corresponding build in the build
2549 history, and each element of the dictionary must match the
2550 corresponding attribute of the build.
2551
2552 """
James E. Blair3158e282016-08-19 09:34:11 -07002553 try:
2554 self.assertEqual(len(self.builds), len(builds))
2555 for i, d in enumerate(builds):
2556 for k, v in d.items():
2557 self.assertEqual(
2558 getattr(self.builds[i], k), v,
2559 "Element %i in builds does not match" % (i,))
2560 except Exception:
2561 for build in self.builds:
2562 self.log.error("Running build: %s" % build)
2563 else:
2564 self.log.error("No running builds")
2565 raise
James E. Blair2b2a8ab2016-08-11 14:39:11 -07002566
James E. Blairb536ecc2016-08-31 10:11:42 -07002567 def assertHistory(self, history, ordered=True):
James E. Blair2b2a8ab2016-08-11 14:39:11 -07002568 """Assert that the completed builds are as described.
2569
2570 The list of completed builds is examined and must match
2571 exactly the list of builds described by the input.
2572
2573 :arg list history: A list of dictionaries. Each item in the
2574 list must match the corresponding build in the build
2575 history, and each element of the dictionary must match the
2576 corresponding attribute of the build.
2577
James E. Blairb536ecc2016-08-31 10:11:42 -07002578 :arg bool ordered: If true, the history must match the order
2579 supplied, if false, the builds are permitted to have
2580 arrived in any order.
2581
James E. Blair2b2a8ab2016-08-11 14:39:11 -07002582 """
James E. Blairb536ecc2016-08-31 10:11:42 -07002583 def matches(history_item, item):
2584 for k, v in item.items():
2585 if getattr(history_item, k) != v:
2586 return False
2587 return True
James E. Blair3158e282016-08-19 09:34:11 -07002588 try:
2589 self.assertEqual(len(self.history), len(history))
James E. Blairb536ecc2016-08-31 10:11:42 -07002590 if ordered:
2591 for i, d in enumerate(history):
2592 if not matches(self.history[i], d):
2593 raise Exception(
K Jonathan Harkercc3a6f02017-02-22 19:08:06 -08002594 "Element %i in history does not match %s" %
2595 (i, self.history[i]))
James E. Blairb536ecc2016-08-31 10:11:42 -07002596 else:
2597 unseen = self.history[:]
2598 for i, d in enumerate(history):
2599 found = False
2600 for unseen_item in unseen:
2601 if matches(unseen_item, d):
2602 found = True
2603 unseen.remove(unseen_item)
2604 break
2605 if not found:
2606 raise Exception("No match found for element %i "
2607 "in history" % (i,))
2608 if unseen:
2609 raise Exception("Unexpected items in history")
James E. Blair3158e282016-08-19 09:34:11 -07002610 except Exception:
2611 for build in self.history:
2612 self.log.error("Completed build: %s" % build)
2613 else:
2614 self.log.error("No completed builds")
2615 raise
James E. Blair2b2a8ab2016-08-11 14:39:11 -07002616
James E. Blair6ac368c2016-12-22 18:07:20 -08002617 def printHistory(self):
2618 """Log the build history.
2619
2620 This can be useful during tests to summarize what jobs have
2621 completed.
2622
2623 """
2624 self.log.debug("Build history:")
2625 for build in self.history:
2626 self.log.debug(build)
2627
James E. Blair59fdbac2015-12-07 17:08:06 -08002628 def getPipeline(self, name):
James E. Blairf84026c2015-12-08 16:11:46 -08002629 return self.sched.abide.tenants.values()[0].layout.pipelines.get(name)
2630
James E. Blair9ea70072017-04-19 16:05:30 -07002631 def updateConfigLayout(self, path):
James E. Blairf84026c2015-12-08 16:11:46 -08002632 root = os.path.join(self.test_root, "config")
Clint Byrumead6c562017-02-01 16:34:04 -08002633 if not os.path.exists(root):
2634 os.makedirs(root)
James E. Blairf84026c2015-12-08 16:11:46 -08002635 f = tempfile.NamedTemporaryFile(dir=root, delete=False)
2636 f.write("""
Paul Belanger66e95962016-11-11 12:11:06 -05002637- tenant:
2638 name: openstack
2639 source:
2640 gerrit:
James E. Blair109da3f2017-04-04 14:39:43 -07002641 config-projects:
Paul Belanger66e95962016-11-11 12:11:06 -05002642 - %s
James E. Blair109da3f2017-04-04 14:39:43 -07002643 untrusted-projects:
James E. Blair0ffa0102017-03-30 13:11:33 -07002644 - org/project
2645 - org/project1
James E. Blair7cb84542017-04-19 13:35:05 -07002646 - org/project2\n""" % path)
James E. Blairf84026c2015-12-08 16:11:46 -08002647 f.close()
James E. Blair39840362017-06-23 20:34:02 +01002648 self.config.set('scheduler', 'tenant_config',
Paul Belanger66e95962016-11-11 12:11:06 -05002649 os.path.join(FIXTURE_DIR, f.name))
Ricardo Carrillo Cruz22994f92016-12-02 11:41:58 +00002650 self.setupAllProjectKeys()
James E. Blair14abdf42015-12-09 16:11:53 -08002651
James E. Blair8b1dc3f2016-07-05 16:49:00 -07002652 def addCommitToRepo(self, project, message, files,
2653 branch='master', tag=None):
James E. Blair14abdf42015-12-09 16:11:53 -08002654 path = os.path.join(self.upstream_root, project)
2655 repo = git.Repo(path)
2656 repo.head.reference = branch
2657 zuul.merger.merger.reset_repo_to_head(repo)
2658 for fn, content in files.items():
2659 fn = os.path.join(path, fn)
James E. Blairc73c73a2017-01-20 15:15:15 -08002660 try:
2661 os.makedirs(os.path.dirname(fn))
2662 except OSError:
2663 pass
James E. Blair14abdf42015-12-09 16:11:53 -08002664 with open(fn, 'w') as f:
2665 f.write(content)
2666 repo.index.add([fn])
2667 commit = repo.index.commit(message)
Clint Byrum58264dc2017-02-07 21:21:22 -08002668 before = repo.heads[branch].commit
James E. Blair14abdf42015-12-09 16:11:53 -08002669 repo.heads[branch].commit = commit
2670 repo.head.reference = branch
2671 repo.git.clean('-x', '-f', '-d')
2672 repo.heads[branch].checkout()
James E. Blair8b1dc3f2016-07-05 16:49:00 -07002673 if tag:
2674 repo.create_tag(tag)
Clint Byrum58264dc2017-02-07 21:21:22 -08002675 return before
2676
James E. Blair9ea0d0b2017-04-20 09:27:15 -07002677 def commitConfigUpdate(self, project_name, source_name):
2678 """Commit an update to zuul.yaml
2679
2680 This overwrites the zuul.yaml in the specificed project with
2681 the contents specified.
2682
2683 :arg str project_name: The name of the project containing
2684 zuul.yaml (e.g., common-config)
2685
2686 :arg str source_name: The path to the file (underneath the
2687 test fixture directory) whose contents should be used to
2688 replace zuul.yaml.
2689 """
2690
2691 source_path = os.path.join(FIXTURE_DIR, source_name)
James E. Blairdfdfcfc2017-04-20 10:19:20 -07002692 files = {}
2693 with open(source_path, 'r') as f:
2694 data = f.read()
2695 layout = yaml.safe_load(data)
2696 files['zuul.yaml'] = data
2697 for item in layout:
2698 if 'job' in item:
2699 jobname = item['job']['name']
2700 files['playbooks/%s.yaml' % jobname] = ''
James E. Blair9ea0d0b2017-04-20 09:27:15 -07002701 before = self.addCommitToRepo(
2702 project_name, 'Pulling content from %s' % source_name,
James E. Blairdfdfcfc2017-04-20 10:19:20 -07002703 files)
James E. Blair9ea0d0b2017-04-20 09:27:15 -07002704 return before
2705
James E. Blair7fc8daa2016-08-08 15:37:15 -07002706 def addEvent(self, connection, event):
James E. Blair9ea0d0b2017-04-20 09:27:15 -07002707
James E. Blair7fc8daa2016-08-08 15:37:15 -07002708 """Inject a Fake (Gerrit) event.
2709
2710 This method accepts a JSON-encoded event and simulates Zuul
2711 having received it from Gerrit. It could (and should)
2712 eventually apply to any connection type, but is currently only
2713 used with Gerrit connections. The name of the connection is
2714 used to look up the corresponding server, and the event is
2715 simulated as having been received by all Zuul connections
2716 attached to that server. So if two Gerrit connections in Zuul
2717 are connected to the same Gerrit server, and you invoke this
2718 method specifying the name of one of them, the event will be
2719 received by both.
2720
2721 .. note::
2722
2723 "self.fake_gerrit.addEvent" calls should be migrated to
2724 this method.
2725
2726 :arg str connection: The name of the connection corresponding
Clark Boylan500992b2017-04-03 14:28:24 -07002727 to the gerrit server.
James E. Blair7fc8daa2016-08-08 15:37:15 -07002728 :arg str event: The JSON-encoded event.
2729
2730 """
2731 specified_conn = self.connections.connections[connection]
2732 for conn in self.connections.connections.values():
2733 if (isinstance(conn, specified_conn.__class__) and
2734 specified_conn.server == conn.server):
2735 conn.addEvent(event)
2736
James E. Blaird8af5422017-05-24 13:59:40 -07002737 def getUpstreamRepos(self, projects):
2738 """Return upstream git repo objects for the listed projects
2739
2740 :arg list projects: A list of strings, each the canonical name
2741 of a project.
2742
2743 :returns: A dictionary of {name: repo} for every listed
2744 project.
2745 :rtype: dict
2746
2747 """
2748
2749 repos = {}
2750 for project in projects:
2751 # FIXME(jeblair): the upstream root does not yet have a
2752 # hostname component; that needs to be added, and this
2753 # line removed:
2754 tmp_project_name = '/'.join(project.split('/')[1:])
2755 path = os.path.join(self.upstream_root, tmp_project_name)
2756 repo = git.Repo(path)
2757 repos[project] = repo
2758 return repos
2759
James E. Blair3f876d52016-07-22 13:07:14 -07002760
2761class AnsibleZuulTestCase(ZuulTestCase):
Paul Belanger174a8272017-03-14 13:20:10 -04002762 """ZuulTestCase but with an actual ansible executor running"""
James E. Blaire1767bc2016-08-02 10:00:27 -07002763 run_ansible = True
Joshua Hesketh25695cb2017-03-06 12:50:04 +11002764
Joshua Heskethd78b4482015-09-14 16:56:34 -06002765
Paul Belanger0a21f0a2017-06-13 13:14:42 -04002766class SSLZuulTestCase(ZuulTestCase):
Paul Belangerd3232f52017-06-14 13:54:31 -04002767 """ZuulTestCase but using SSL when possible"""
Paul Belanger0a21f0a2017-06-13 13:14:42 -04002768 use_ssl = True
2769
2770
Joshua Heskethd78b4482015-09-14 16:56:34 -06002771class ZuulDBTestCase(ZuulTestCase):
James E. Blair82844892017-03-06 10:55:26 -08002772 def setup_config(self):
2773 super(ZuulDBTestCase, self).setup_config()
Joshua Heskethd78b4482015-09-14 16:56:34 -06002774 for section_name in self.config.sections():
2775 con_match = re.match(r'^connection ([\'\"]?)(.*)(\1)$',
2776 section_name, re.I)
2777 if not con_match:
2778 continue
2779
2780 if self.config.get(section_name, 'driver') == 'sql':
2781 f = MySQLSchemaFixture()
2782 self.useFixture(f)
2783 if (self.config.get(section_name, 'dburi') ==
2784 '$MYSQL_FIXTURE_DBURI$'):
2785 self.config.set(section_name, 'dburi', f.dburi)