summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xtests/base.py29
-rwxr-xr-xtests/unit/test_executor.py20
-rw-r--r--tests/unit/test_github_driver.py26
-rw-r--r--tests/unit/test_push_reqs.py9
-rwxr-xr-xtests/unit/test_scheduler.py18
-rw-r--r--zuul/executor/server.py16
-rw-r--r--zuul/manager/__init__.py24
-rw-r--r--zuul/manager/independent.py3
-rw-r--r--zuul/merger/client.py5
-rw-r--r--zuul/merger/merger.py72
-rw-r--r--zuul/merger/server.py12
11 files changed, 185 insertions, 49 deletions
diff --git a/tests/base.py b/tests/base.py
index f13762379..4b06c2883 100755
--- a/tests/base.py
+++ b/tests/base.py
@@ -139,7 +139,8 @@ class FakeGerritChange(object):
'Verified': ('Verified', -2, 2)}
def __init__(self, gerrit, number, project, branch, subject,
- status='NEW', upstream_root=None, files={}):
+ status='NEW', upstream_root=None, files={},
+ parent=None):
self.gerrit = gerrit
self.source = gerrit
self.reported = 0
@@ -174,16 +175,18 @@ class FakeGerritChange(object):
'url': 'https://hostname/%s' % number}
self.upstream_root = upstream_root
- self.addPatchset(files=files)
+ self.addPatchset(files=files, parent=parent)
self.data['submitRecords'] = self.getSubmitRecords()
self.open = status == 'NEW'
- def addFakeChangeToRepo(self, msg, files, large):
+ def addFakeChangeToRepo(self, msg, files, large, parent):
path = os.path.join(self.upstream_root, self.project)
repo = git.Repo(path)
+ if parent is None:
+ parent = 'refs/tags/init'
ref = GerritChangeReference.create(
repo, '1/%s/%s' % (self.number, self.latest_patchset),
- 'refs/tags/init')
+ parent)
repo.head.reference = ref
zuul.merger.merger.reset_repo_to_head(repo)
repo.git.clean('-x', '-f', '-d')
@@ -211,7 +214,7 @@ class FakeGerritChange(object):
repo.heads['master'].checkout()
return r
- def addPatchset(self, files=None, large=False):
+ def addPatchset(self, files=None, large=False, parent=None):
self.latest_patchset += 1
if not files:
fn = '%s-%s' % (self.branch.replace('/', '_'), self.number)
@@ -219,7 +222,7 @@ class FakeGerritChange(object):
(self.branch, self.number, self.latest_patchset))
files = {fn: data}
msg = self.subject + '-' + str(self.latest_patchset)
- c = self.addFakeChangeToRepo(msg, files, large)
+ c = self.addFakeChangeToRepo(msg, files, large, parent)
ps_files = [{'file': '/COMMIT_MSG',
'type': 'ADDED'},
{'file': 'README',
@@ -469,12 +472,12 @@ class FakeGerritConnection(gerritconnection.GerritConnection):
self.upstream_root = upstream_root
def addFakeChange(self, project, branch, subject, status='NEW',
- files=None):
+ files=None, parent=None):
"""Add a change to the fake Gerrit."""
self.change_number += 1
c = FakeGerritChange(self, self.change_number, project, branch,
subject, upstream_root=self.upstream_root,
- status=status, files=files)
+ status=status, files=files, parent=parent)
self.changes[self.change_number] = c
return c
@@ -955,6 +958,13 @@ class FakeGithubPullRequest(object):
}
return (name, data)
+ def setMerged(self, commit_message):
+ self.is_merged = True
+ self.merge_message = commit_message
+
+ repo = self._getRepo()
+ repo.heads[self.branch].commit = repo.commit(self.head_sha)
+
class FakeGithubConnection(githubconnection.GithubConnection):
log = logging.getLogger("zuul.test.FakeGithubConnection")
@@ -1102,8 +1112,7 @@ class FakeGithubConnection(githubconnection.GithubConnection):
self.merge_not_allowed_count -= 1
raise MergeFailure('Merge was not successful due to mergeability'
' conflict')
- pull_request.is_merged = True
- pull_request.merge_message = commit_message
+ pull_request.setMerged(commit_message)
def setCommitStatus(self, project, sha, state, url='', description='',
context='default', user='zuul'):
diff --git a/tests/unit/test_executor.py b/tests/unit/test_executor.py
index 444d78372..3793edc1d 100755
--- a/tests/unit/test_executor.py
+++ b/tests/unit/test_executor.py
@@ -348,19 +348,31 @@ class TestExecutorRepos(ZuulTestCase):
p1 = "review.example.com/org/project1"
p2 = "review.example.com/org/project2"
projects = [p1, p2]
+ upstream = self.getUpstreamRepos(projects)
A = self.fake_gerrit.addFakeChange('org/project1', 'master', 'A')
event = A.getRefUpdatedEvent()
A.setMerged()
+ A_commit = str(upstream[p1].commit('master'))
+ self.log.debug("A commit: %s" % A_commit)
+
+ # Add another commit to the repo that merged right after this
+ # one to make sure that our post job runs with the one that we
+ # intended rather than simply the current repo state.
+ B = self.fake_gerrit.addFakeChange('org/project1', 'master', 'B',
+ parent='refs/changes/1/1/1')
+ B.setMerged()
+ B_commit = str(upstream[p1].commit('master'))
+ self.log.debug("B commit: %s" % B_commit)
+
self.fake_gerrit.addEvent(event)
self.waitUntilSettled()
- upstream = self.getUpstreamRepos(projects)
states = [
- {p1: dict(commit=str(upstream[p1].commit('master')),
- present=[A], branch='master'),
+ {p1: dict(commit=A_commit,
+ present=[A], absent=[B], branch='master'),
p2: dict(commit=str(upstream[p2].commit('master')),
- absent=[A], branch='master'),
+ absent=[A, B], branch='master'),
},
]
diff --git a/tests/unit/test_github_driver.py b/tests/unit/test_github_driver.py
index d52e38e7c..538de5866 100644
--- a/tests/unit/test_github_driver.py
+++ b/tests/unit/test_github_driver.py
@@ -125,16 +125,20 @@ class TestGithubDriver(ZuulTestCase):
def test_push_event(self):
self.executor_server.hold_jobs_in_build = True
- old_sha = random_sha1()
- new_sha = random_sha1()
- self.fake_github.emitEvent(
- self.fake_github.getPushEvent('org/project', 'refs/heads/master',
- old_sha, new_sha))
+ A = self.fake_github.openFakePullRequest('org/project', 'master', 'A')
+ old_sha = '0' * 40
+ new_sha = A.head_sha
+ A.setMerged("merging A")
+ pevent = self.fake_github.getPushEvent(project='org/project',
+ ref='refs/heads/master',
+ old_rev=old_sha,
+ new_rev=new_sha)
+ self.fake_github.emitEvent(pevent)
self.waitUntilSettled()
build_params = self.builds[0].parameters
self.assertEqual('refs/heads/master', build_params['zuul']['ref'])
- self.assertEqual(old_sha, build_params['zuul']['oldrev'])
+ self.assertFalse('oldrev' in build_params['zuul'])
self.assertEqual(new_sha, build_params['zuul']['newrev'])
self.executor_server.hold_jobs_in_build = False
@@ -371,9 +375,15 @@ class TestGithubDriver(ZuulTestCase):
project = 'org/project2'
# pipeline reports pull status both on start and success
self.executor_server.hold_jobs_in_build = True
- pevent = self.fake_github.getPushEvent(project=project,
- ref='refs/heads/master')
+ A = self.fake_github.openFakePullRequest(project, 'master', 'A')
+ old_sha = '0' * 40
+ new_sha = A.head_sha
+ A.setMerged("merging A")
+ pevent = self.fake_github.getPushEvent(project=project,
+ ref='refs/heads/master',
+ old_rev=old_sha,
+ new_rev=new_sha)
self.fake_github.emitEvent(pevent)
self.waitUntilSettled()
diff --git a/tests/unit/test_push_reqs.py b/tests/unit/test_push_reqs.py
index d3a1febe8..80c3be97c 100644
--- a/tests/unit/test_push_reqs.py
+++ b/tests/unit/test_push_reqs.py
@@ -25,12 +25,13 @@ class TestPushRequirements(ZuulTestCase):
def test_push_requirements(self):
self.executor_server.hold_jobs_in_build = True
- # Create a github change, add a change and emit a push event
A = self.fake_github.openFakePullRequest('org/project1', 'master', 'A')
- old_sha = A.head_sha
+ new_sha = A.head_sha
+ A.setMerged("merging A")
pevent = self.fake_github.getPushEvent(project='org/project1',
ref='refs/heads/master',
- old_rev=old_sha)
+ new_rev=new_sha)
+
self.fake_github.emitEvent(pevent)
self.waitUntilSettled()
@@ -43,7 +44,7 @@ class TestPushRequirements(ZuulTestCase):
# Make a gerrit change, and emit a ref-updated event
B = self.fake_gerrit.addFakeChange('org/project2', 'master', 'B')
self.fake_gerrit.addEvent(B.getRefUpdatedEvent())
-
+ B.setMerged()
self.waitUntilSettled()
# All but one pipeline should be skipped, increasing builds by 1
diff --git a/tests/unit/test_scheduler.py b/tests/unit/test_scheduler.py
index e7cc93dc9..93367b918 100755
--- a/tests/unit/test_scheduler.py
+++ b/tests/unit/test_scheduler.py
@@ -1103,6 +1103,12 @@ class TestScheduler(ZuulTestCase):
def test_post(self):
"Test that post jobs run"
+ p = "review.example.com/org/project"
+ upstream = self.getUpstreamRepos([p])
+ A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
+ A.setMerged()
+ A_commit = str(upstream[p].commit('master'))
+ self.log.debug("A commit: %s" % A_commit)
e = {
"type": "ref-updated",
@@ -1111,7 +1117,7 @@ class TestScheduler(ZuulTestCase):
},
"refUpdate": {
"oldRev": "90f173846e3af9154517b88543ffbd1691f31366",
- "newRev": "d479a0bfcb34da57a31adb2a595c0cf687812543",
+ "newRev": A_commit,
"refName": "master",
"project": "org/project",
}
@@ -1156,7 +1162,7 @@ class TestScheduler(ZuulTestCase):
"refUpdate": {
"oldRev": "90f173846e3af9154517b88543ffbd1691f31366",
"newRev": "0000000000000000000000000000000000000000",
- "refName": "master",
+ "refName": "testbranch",
"project": "org/project",
}
}
@@ -3080,6 +3086,12 @@ class TestScheduler(ZuulTestCase):
def test_client_enqueue_ref(self):
"Test that the RPC client can enqueue a ref"
+ p = "review.example.com/org/project"
+ upstream = self.getUpstreamRepos([p])
+ A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
+ A.setMerged()
+ A_commit = str(upstream[p].commit('master'))
+ self.log.debug("A commit: %s" % A_commit)
client = zuul.rpcclient.RPCClient('127.0.0.1',
self.gearman_server.port)
@@ -3091,7 +3103,7 @@ class TestScheduler(ZuulTestCase):
trigger='gerrit',
ref='master',
oldrev='90f173846e3af9154517b88543ffbd1691f31366',
- newrev='d479a0bfcb34da57a31adb2a595c0cf687812543')
+ newrev=A_commit)
self.waitUntilSettled()
job_names = [x.name for x in self.history]
self.assertEqual(len(self.history), 1)
diff --git a/zuul/executor/server.py b/zuul/executor/server.py
index b16611154..8d23cb77c 100644
--- a/zuul/executor/server.py
+++ b/zuul/executor/server.py
@@ -625,6 +625,7 @@ class ExecutorServer(object):
self.hostname)
self.merger_worker.registerFunction("merger:merge")
self.merger_worker.registerFunction("merger:cat")
+ self.merger_worker.registerFunction("merger:refstate")
def stop(self):
self.log.debug("Stopping")
@@ -721,6 +722,9 @@ class ExecutorServer(object):
elif job.name == 'merger:merge':
self.log.debug("Got merge job: %s" % job.unique)
self.merge(job)
+ elif job.name == 'merger:refstate':
+ self.log.debug("Got refstate job: %s" % job.unique)
+ self.refstate(job)
else:
self.log.error("Unable to handle job %s" % job.name)
job.sendWorkFail()
@@ -800,6 +804,14 @@ class ExecutorServer(object):
files=files)
job.sendWorkComplete(json.dumps(result))
+ def refstate(self, job):
+ args = json.loads(job.arguments)
+ with self.merger_lock:
+ success, repo_state = self.merger.getRepoState(args['items'])
+ result = dict(updated=success,
+ repo_state=repo_state)
+ job.sendWorkComplete(json.dumps(result))
+
def merge(self, job):
args = json.loads(job.arguments)
with self.merger_lock:
@@ -954,6 +966,10 @@ class AnsibleJob(object):
# a work complete result, don't run any jobs
return
+ state_items = [i for i in args['items'] if not i.get('number')]
+ if state_items:
+ merger.setRepoState(state_items, args['repo_state'])
+
for project in args['projects']:
repo = repos[project['canonical_name']]
# If this project is the Zuul project and this is a ref
diff --git a/zuul/manager/__init__.py b/zuul/manager/__init__.py
index dfb3238a2..8282f86a4 100644
--- a/zuul/manager/__init__.py
+++ b/zuul/manager/__init__.py
@@ -13,6 +13,7 @@
import logging
from zuul import exceptions
+from zuul import model
class DynamicChangeQueueContextManager(object):
@@ -483,20 +484,18 @@ class PipelineManager(object):
def scheduleMerge(self, item, files=None, dirs=None):
build_set = item.current_build_set
- if not hasattr(item.change, 'branch'):
- self.log.debug("Change %s does not have an associated branch, "
- "not scheduling a merge job for item %s" %
- (item.change, item))
- build_set.merge_state = build_set.COMPLETE
- return True
-
self.log.debug("Scheduling merge for item %s (files: %s, dirs: %s)" %
(item, files, dirs))
build_set = item.current_build_set
build_set.merge_state = build_set.PENDING
- self.sched.merger.mergeChanges(build_set.merger_items,
- item.current_build_set, files, dirs,
- precedence=self.pipeline.precedence)
+ if isinstance(item.change, model.Change):
+ self.sched.merger.mergeChanges(build_set.merger_items,
+ item.current_build_set, files, dirs,
+ precedence=self.pipeline.precedence)
+ else:
+ self.sched.merger.getRepoState(build_set.merger_items,
+ item.current_build_set,
+ precedence=self.pipeline.precedence)
return False
def prepareItem(self, item):
@@ -675,12 +674,13 @@ class PipelineManager(object):
build_set = event.build_set
item = build_set.item
build_set.merge_state = build_set.COMPLETE
+ build_set.repo_state = event.repo_state
if event.merged:
build_set.commit = event.commit
build_set.files.setFiles(event.files)
- build_set.repo_state = event.repo_state
elif event.updated:
- build_set.commit = item.change.newrev
+ build_set.commit = (item.change.newrev or
+ '0000000000000000000000000000000000000000')
if not build_set.commit:
self.log.info("Unable to merge change %s" % item.change)
item.setUnableToMerge()
diff --git a/zuul/manager/independent.py b/zuul/manager/independent.py
index 06c9a01a1..7b0a9f53c 100644
--- a/zuul/manager/independent.py
+++ b/zuul/manager/independent.py
@@ -44,6 +44,9 @@ class IndependentPipelineManager(PipelineManager):
if hasattr(change, 'number'):
history = history or []
history.append(change.number)
+ else:
+ # Don't enqueue dependencies ahead of a non-change ref.
+ return True
ret = self.checkForChangesNeededBy(change, change_queue)
if ret in [True, False]:
diff --git a/zuul/merger/client.py b/zuul/merger/client.py
index dd9c8d551..5191a44f9 100644
--- a/zuul/merger/client.py
+++ b/zuul/merger/client.py
@@ -116,6 +116,11 @@ class MergeClient(object):
repo_state=repo_state)
self.submitJob('merger:merge', data, build_set, precedence)
+ def getRepoState(self, items, build_set,
+ precedence=zuul.model.PRECEDENCE_NORMAL):
+ data = dict(items=items)
+ self.submitJob('merger:refstate', data, build_set, precedence)
+
def getFiles(self, connection_name, project_name, branch, files, dirs=[],
precedence=zuul.model.PRECEDENCE_HIGH):
data = dict(connection=connection_name,
diff --git a/zuul/merger/merger.py b/zuul/merger/merger.py
index c5d1f2ad5..ed98696ec 100644
--- a/zuul/merger/merger.py
+++ b/zuul/merger/merger.py
@@ -20,6 +20,8 @@ import logging
import zuul.model
+NULL_REF = '0000000000000000000000000000000000000000'
+
def reset_repo_to_head(repo):
# This lets us reset the repo even if there is a file in the root
@@ -178,8 +180,13 @@ class Repo(object):
self.setRef(path, hexsha, repo)
unseen.discard(path)
for path in unseen:
- self.log.debug("Delete reference %s", path)
- git.refs.SymbolicReference.delete(repo, ref.path)
+ self.deleteRef(path, repo)
+
+ def deleteRef(self, path, repo=None):
+ if repo is None:
+ repo = self.createRepoObject()
+ self.log.debug("Delete reference %s", path)
+ git.refs.SymbolicReference.delete(repo, path)
def checkout(self, ref):
repo = self.createRepoObject()
@@ -369,6 +376,16 @@ class Merger(object):
recent[key] = ref.object
project[ref.path] = ref.object.hexsha
+ def _alterRepoState(self, connection_name, project_name,
+ repo_state, path, hexsha):
+ projects = repo_state.setdefault(connection_name, {})
+ project = projects.setdefault(project_name, {})
+ if hexsha == NULL_REF:
+ if path in project:
+ del project[path]
+ else:
+ project[path] = hexsha
+
def _restoreRepoState(self, connection_name, project_name, repo,
repo_state):
projects = repo_state.get(connection_name, {})
@@ -470,12 +487,8 @@ class Merger(object):
if repo_state is None:
repo_state = {}
for item in items:
- if item.get("number") and item.get("patchset"):
- self.log.debug("Merging for change %s,%s." %
- (item["number"], item["patchset"]))
- elif item.get("newrev") and item.get("oldrev"):
- self.log.debug("Merging for rev %s with oldrev %s." %
- (item["newrev"], item["oldrev"]))
+ self.log.debug("Merging for change %s,%s" %
+ (item["number"], item["patchset"]))
commit = self._mergeItem(item, recent, repo_state)
if not commit:
return None
@@ -492,6 +505,49 @@ class Merger(object):
ret_recent[k] = v.hexsha
return commit.hexsha, read_files, repo_state, ret_recent
+ def setRepoState(self, items, repo_state):
+ # Sets the repo state for the items
+ seen = set()
+ for item in items:
+ repo = self.getRepo(item['connection'], item['project'])
+ key = (item['connection'], item['project'], item['branch'])
+
+ if key in seen:
+ continue
+
+ repo.reset()
+ self._restoreRepoState(item['connection'], item['project'], repo,
+ repo_state)
+
+ def getRepoState(self, items):
+ # Gets the repo state for items. Generally this will be
+ # called in any non-change pipeline. We will return the repo
+ # state for each item, but manipulated with any information in
+ # the item (eg, if it creates a ref, that will be in the repo
+ # state regardless of the actual state).
+ seen = set()
+ recent = {}
+ repo_state = {}
+ for item in items:
+ repo = self.getRepo(item['connection'], item['project'])
+ key = (item['connection'], item['project'], item['branch'])
+ if key not in seen:
+ try:
+ repo.reset()
+ except Exception:
+ self.log.exception("Unable to reset repo %s" % repo)
+ return (False, {})
+
+ self._saveRepoState(item['connection'], item['project'], repo,
+ repo_state, recent)
+
+ if item.get('newrev'):
+ # This is a ref update rather than a branch tip, so make sure
+ # our returned state includes this change.
+ self._alterRepoState(item['connection'], item['project'],
+ repo_state, item['ref'], item['newrev'])
+ return (True, repo_state)
+
def getFiles(self, connection_name, project_name, branch, files, dirs=[]):
repo = self.getRepo(connection_name, project_name)
return repo.getFiles(files, dirs, branch=branch)
diff --git a/zuul/merger/server.py b/zuul/merger/server.py
index c342e1ac8..fc599c117 100644
--- a/zuul/merger/server.py
+++ b/zuul/merger/server.py
@@ -58,6 +58,7 @@ class MergeServer(object):
def register(self):
self.worker.registerFunction("merger:merge")
self.worker.registerFunction("merger:cat")
+ self.worker.registerFunction("merger:refstate")
def stop(self):
self.log.debug("Stopping")
@@ -80,6 +81,9 @@ class MergeServer(object):
elif job.name == 'merger:cat':
self.log.debug("Got cat job: %s" % job.unique)
self.cat(job)
+ elif job.name == 'merger:refstate':
+ self.log.debug("Got refstate job: %s" % job.unique)
+ self.refstate(job)
else:
self.log.error("Unable to handle job %s" % job.name)
job.sendWorkFail()
@@ -104,6 +108,14 @@ class MergeServer(object):
recent) = ret
job.sendWorkComplete(json.dumps(result))
+ def refstate(self, job):
+ args = json.loads(job.arguments)
+
+ success, repo_state = self.merger.getItemRepoState(args['items'])
+ result = dict(updated=success,
+ repo_state=repo_state)
+ job.sendWorkComplete(json.dumps(result))
+
def cat(self, job):
args = json.loads(job.arguments)
self.merger.updateRepo(args['connection'], args['project'])