Merge "Fix constructor arguments to source" into feature/zuulv3
diff --git a/tests/base.py b/tests/base.py
index 9bd44f6..1f447da 100755
--- a/tests/base.py
+++ b/tests/base.py
@@ -771,12 +771,15 @@
class RecordingAnsibleJob(zuul.executor.server.AnsibleJob):
- def runPlaybooks(self, args):
+ def doMergeChanges(self, items):
+ # Get a merger in order to update the repos involved in this job.
+ commit = super(RecordingAnsibleJob, self).doMergeChanges(items)
+ if not commit: # merge conflict
+ self.recordResult('MERGER_FAILURE')
+ return commit
+
+ def recordResult(self, result):
build = self.executor_server.job_builds[self.job.unique]
- build.jobdir = self.jobdir
-
- result = super(RecordingAnsibleJob, self).runPlaybooks(args)
-
self.executor_server.lock.acquire()
self.executor_server.build_history.append(
BuildHistory(name=build.name, result=result, changes=build.changes,
@@ -787,6 +790,13 @@
self.executor_server.running_builds.remove(build)
del self.executor_server.job_builds[self.job.unique]
self.executor_server.lock.release()
+
+ def runPlaybooks(self, args):
+ build = self.executor_server.job_builds[self.job.unique]
+ build.jobdir = self.jobdir
+
+ result = super(RecordingAnsibleJob, self).runPlaybooks(args)
+ self.recordResult(result)
return result
def runAnsible(self, cmd, timeout, trusted=False):
diff --git a/tests/fixtures/config/single-tenant/git/common-config/zuul.yaml b/tests/fixtures/config/single-tenant/git/common-config/zuul.yaml
index 47c173d..dff18de 100644
--- a/tests/fixtures/config/single-tenant/git/common-config/zuul.yaml
+++ b/tests/fixtures/config/single-tenant/git/common-config/zuul.yaml
@@ -151,6 +151,15 @@
- project:
name: org/project2
+ check:
+ jobs:
+ - project-merge
+ - project-test1:
+ dependencies: project-merge
+ - project-test2:
+ dependencies: project-merge
+ - project1-project2-integration:
+ dependencies: project-merge
gate:
queue: integrated
jobs:
diff --git a/tests/fixtures/config/single-tenant/git/layout-ignore-dependencies/zuul.yaml b/tests/fixtures/config/single-tenant/git/layout-ignore-dependencies/zuul.yaml
new file mode 100644
index 0000000..4010372
--- /dev/null
+++ b/tests/fixtures/config/single-tenant/git/layout-ignore-dependencies/zuul.yaml
@@ -0,0 +1,66 @@
+- pipeline:
+ name: check
+ manager: independent
+ ignore-dependencies: true
+ source: gerrit
+ trigger:
+ gerrit:
+ - event: patchset-created
+ success:
+ gerrit:
+ verified: 1
+ failure:
+ gerrit:
+ verified: -1
+
+- job:
+ name: project1-merge
+
+- job:
+ name: project1-test1
+
+- job:
+ name: project1-test2
+
+- job:
+ name: project2-merge
+
+- job:
+ name: project2-test1
+
+- job:
+ name: project2-test2
+
+- job:
+ name: project1-project2-integration
+ queue-name: integration
+
+- project:
+ name: org/project1
+ check:
+ jobs:
+ - project1-merge
+ - project1-test1:
+ dependencies:
+ - project1-merge
+ - project1-test2:
+ dependencies:
+ - project1-merge
+ - project1-project2-integration:
+ dependencies:
+ - project1-merge
+
+- project:
+ name: org/project2
+ check:
+ jobs:
+ - project2-merge
+ - project2-test1:
+ dependencies:
+ - project2-merge
+ - project2-test2:
+ dependencies:
+ - project2-merge
+ - project1-project2-integration:
+ dependencies:
+ - project2-merge
diff --git a/tests/fixtures/config/single-tenant/git/layout-live-reconfiguration-del-project/playbooks/project-merge.yaml b/tests/fixtures/config/single-tenant/git/layout-live-reconfiguration-del-project/playbooks/project-merge.yaml
new file mode 100644
index 0000000..f679dce
--- /dev/null
+++ b/tests/fixtures/config/single-tenant/git/layout-live-reconfiguration-del-project/playbooks/project-merge.yaml
@@ -0,0 +1,2 @@
+- hosts: all
+ tasks: []
diff --git a/tests/fixtures/config/single-tenant/git/layout-live-reconfiguration-del-project/playbooks/project-test1.yaml b/tests/fixtures/config/single-tenant/git/layout-live-reconfiguration-del-project/playbooks/project-test1.yaml
new file mode 100644
index 0000000..f679dce
--- /dev/null
+++ b/tests/fixtures/config/single-tenant/git/layout-live-reconfiguration-del-project/playbooks/project-test1.yaml
@@ -0,0 +1,2 @@
+- hosts: all
+ tasks: []
diff --git a/tests/fixtures/config/single-tenant/git/layout-live-reconfiguration-del-project/playbooks/project-test2.yaml b/tests/fixtures/config/single-tenant/git/layout-live-reconfiguration-del-project/playbooks/project-test2.yaml
new file mode 100644
index 0000000..f679dce
--- /dev/null
+++ b/tests/fixtures/config/single-tenant/git/layout-live-reconfiguration-del-project/playbooks/project-test2.yaml
@@ -0,0 +1,2 @@
+- hosts: all
+ tasks: []
diff --git a/tests/fixtures/config/single-tenant/git/layout-live-reconfiguration-del-project/playbooks/project-testfile.yaml b/tests/fixtures/config/single-tenant/git/layout-live-reconfiguration-del-project/playbooks/project-testfile.yaml
new file mode 100644
index 0000000..f679dce
--- /dev/null
+++ b/tests/fixtures/config/single-tenant/git/layout-live-reconfiguration-del-project/playbooks/project-testfile.yaml
@@ -0,0 +1,2 @@
+- hosts: all
+ tasks: []
diff --git a/tests/fixtures/config/single-tenant/git/layout-live-reconfiguration-del-project/zuul.yaml b/tests/fixtures/config/single-tenant/git/layout-live-reconfiguration-del-project/zuul.yaml
new file mode 100644
index 0000000..a6d6599
--- /dev/null
+++ b/tests/fixtures/config/single-tenant/git/layout-live-reconfiguration-del-project/zuul.yaml
@@ -0,0 +1,42 @@
+- pipeline:
+ name: check
+ manager: independent
+ source: gerrit
+ trigger:
+ gerrit:
+ - event: patchset-created
+ success:
+ gerrit:
+ verified: 1
+ failure:
+ gerrit:
+ verified: -1
+
+- job:
+ name: project-merge
+ hold-following-changes: true
+
+- job:
+ name: project-test1
+
+- job:
+ name: project-test2
+
+- job:
+ name: project-testfile
+
+- project:
+ name: org/project
+ merge-mode: cherry-pick
+ check:
+ jobs:
+ - project-merge
+ - project-test1:
+ dependencies:
+ - project-merge
+ - project-test2:
+ dependencies:
+ - project-merge
+ - project-testfile:
+ dependencies:
+ - project-merge
diff --git a/tests/fixtures/config/single-tenant/git/layout-rate-limit/playbooks/project-merge.yaml b/tests/fixtures/config/single-tenant/git/layout-rate-limit/playbooks/project-merge.yaml
new file mode 100644
index 0000000..f679dce
--- /dev/null
+++ b/tests/fixtures/config/single-tenant/git/layout-rate-limit/playbooks/project-merge.yaml
@@ -0,0 +1,2 @@
+- hosts: all
+ tasks: []
diff --git a/tests/fixtures/config/single-tenant/git/layout-rate-limit/playbooks/project-test1.yaml b/tests/fixtures/config/single-tenant/git/layout-rate-limit/playbooks/project-test1.yaml
new file mode 100644
index 0000000..f679dce
--- /dev/null
+++ b/tests/fixtures/config/single-tenant/git/layout-rate-limit/playbooks/project-test1.yaml
@@ -0,0 +1,2 @@
+- hosts: all
+ tasks: []
diff --git a/tests/fixtures/config/single-tenant/git/layout-rate-limit/playbooks/project-test2.yaml b/tests/fixtures/config/single-tenant/git/layout-rate-limit/playbooks/project-test2.yaml
new file mode 100644
index 0000000..f679dce
--- /dev/null
+++ b/tests/fixtures/config/single-tenant/git/layout-rate-limit/playbooks/project-test2.yaml
@@ -0,0 +1,2 @@
+- hosts: all
+ tasks: []
diff --git a/tests/fixtures/config/single-tenant/git/layout-rate-limit/zuul.yaml b/tests/fixtures/config/single-tenant/git/layout-rate-limit/zuul.yaml
new file mode 100644
index 0000000..c4e00f6
--- /dev/null
+++ b/tests/fixtures/config/single-tenant/git/layout-rate-limit/zuul.yaml
@@ -0,0 +1,47 @@
+- pipeline:
+ name: gate
+ manager: dependent
+ failure-message: Build failed. For information on how to proceed, see http://wiki.example.org/Test_Failures
+ source: gerrit
+ trigger:
+ gerrit:
+ - event: comment-added
+ approval:
+ - approved: 1
+ start:
+ gerrit:
+ verified: 0
+ success:
+ gerrit:
+ verified: 2
+ submit: true
+ failure:
+ gerrit:
+ verified: -2
+ window: 2
+ window-floor: 1
+ window-increase-type: linear
+ window-increase-factor: 1
+ window-decrease-type: exponential
+ window-decrease-factor: 2
+
+- job:
+ name: project-merge
+
+- job:
+ name: project-test1
+
+- job:
+ name: project-test2
+
+- project:
+ name: org/project
+ gate:
+ jobs:
+ - project-merge
+ - project-test1:
+ dependencies:
+ - project-merge
+ - project-test2:
+ dependencies:
+ - project-merge
diff --git a/tests/fixtures/layout-ignore-dependencies.yaml b/tests/fixtures/layout-ignore-dependencies.yaml
deleted file mode 100644
index 5c0257c..0000000
--- a/tests/fixtures/layout-ignore-dependencies.yaml
+++ /dev/null
@@ -1,28 +0,0 @@
-pipelines:
- - name: check
- manager: IndependentPipelineManager
- ignore-dependencies: true
- trigger:
- gerrit:
- - event: patchset-created
- success:
- gerrit:
- verified: 1
- failure:
- gerrit:
- verified: -1
-
-projects:
- - name: org/project1
- check:
- - project1-merge:
- - project1-test1
- - project1-test2
- - project1-project2-integration
-
- - name: org/project2
- check:
- - project2-merge:
- - project2-test1
- - project2-test2
- - project1-project2-integration
diff --git a/tests/fixtures/layout-live-reconfiguration-del-project.yaml b/tests/fixtures/layout-live-reconfiguration-del-project.yaml
deleted file mode 100644
index 07ffb2e..0000000
--- a/tests/fixtures/layout-live-reconfiguration-del-project.yaml
+++ /dev/null
@@ -1,21 +0,0 @@
-pipelines:
- - name: check
- manager: IndependentPipelineManager
- trigger:
- gerrit:
- - event: patchset-created
- success:
- gerrit:
- verified: 1
- failure:
- gerrit:
- verified: -1
-
-projects:
- - name: org/project
- merge-mode: cherry-pick
- check:
- - project-merge:
- - project-test1
- - project-test2
- - project-testfile
diff --git a/tests/fixtures/layout-rate-limit.yaml b/tests/fixtures/layout-rate-limit.yaml
deleted file mode 100644
index 9f6748c..0000000
--- a/tests/fixtures/layout-rate-limit.yaml
+++ /dev/null
@@ -1,32 +0,0 @@
-pipelines:
- - name: gate
- manager: DependentPipelineManager
- failure-message: Build failed. For information on how to proceed, see http://wiki.example.org/Test_Failures
- trigger:
- gerrit:
- - event: comment-added
- approval:
- - approved: 1
- start:
- gerrit:
- verified: 0
- success:
- gerrit:
- verified: 2
- submit: true
- failure:
- gerrit:
- verified: -2
- window: 2
- window-floor: 1
- window-increase-type: linear
- window-increase-factor: 1
- window-decrease-type: exponential
- window-decrease-factor: 2
-
-projects:
- - name: org/project
- gate:
- - project-merge:
- - project-test1
- - project-test2
diff --git a/tests/make_playbooks.py b/tests/make_playbooks.py
index 17acba8..93c37bc 100755
--- a/tests/make_playbooks.py
+++ b/tests/make_playbooks.py
@@ -14,7 +14,7 @@
import os
-import yaml
+from zuul.lib import yamlutil as yaml
FIXTURE_DIR = os.path.join(os.path.dirname(__file__),
'fixtures')
diff --git a/tests/unit/test_model.py b/tests/unit/test_model.py
index f906095..2167a3b 100644
--- a/tests/unit/test_model.py
+++ b/tests/unit/test_model.py
@@ -18,11 +18,11 @@
import fixtures
import testtools
-import yaml
from zuul import model
from zuul import configloader
from zuul.lib import encryption
+from zuul.lib import yamlutil as yaml
from tests.base import BaseTestCase, FIXTURE_DIR
diff --git a/tests/unit/test_scheduler.py b/tests/unit/test_scheduler.py
index 4b3fbf4..43a8ddf 100755
--- a/tests/unit/test_scheduler.py
+++ b/tests/unit/test_scheduler.py
@@ -926,18 +926,17 @@
a = source.getChange(event, refresh=True)
self.assertTrue(source.canMerge(a, mgr.getSubmitAllowNeeds()))
- @skip("Disabled for early v3 development")
- def test_build_configuration_conflict(self):
- "Test that merge conflicts are handled"
+ def test_project_merge_conflict(self):
+ "Test that gate merge conflicts are handled properly"
self.gearman_server.hold_jobs_in_queue = True
- A = self.fake_gerrit.addFakeChange('org/conflict-project',
- 'master', 'A')
- A.addPatchset(['conflict'])
- B = self.fake_gerrit.addFakeChange('org/conflict-project',
- 'master', 'B')
- B.addPatchset(['conflict'])
- C = self.fake_gerrit.addFakeChange('org/conflict-project',
+ A = self.fake_gerrit.addFakeChange('org/project',
+ 'master', 'A',
+ files={'conflict': 'foo'})
+ B = self.fake_gerrit.addFakeChange('org/project',
+ 'master', 'B',
+ files={'conflict': 'bar'})
+ C = self.fake_gerrit.addFakeChange('org/project',
'master', 'C')
A.addApproval('code-review', 2)
B.addApproval('code-review', 2)
@@ -951,15 +950,13 @@
self.assertEqual(B.reported, 1)
self.assertEqual(C.reported, 1)
- self.gearman_server.release('.*-merge')
+ self.gearman_server.release('project-merge')
self.waitUntilSettled()
- self.gearman_server.release('.*-merge')
+ self.gearman_server.release('project-merge')
self.waitUntilSettled()
- self.gearman_server.release('.*-merge')
+ self.gearman_server.release('project-merge')
self.waitUntilSettled()
- self.assertEqual(len(self.history), 2) # A and C merge jobs
-
self.gearman_server.hold_jobs_in_queue = False
self.gearman_server.release()
self.waitUntilSettled()
@@ -970,7 +967,97 @@
self.assertEqual(A.reported, 2)
self.assertEqual(B.reported, 2)
self.assertEqual(C.reported, 2)
- self.assertEqual(len(self.history), 6)
+
+ self.assertHistory([
+ dict(name='project-merge', result='SUCCESS', changes='1,1'),
+ dict(name='project-test1', result='SUCCESS', changes='1,1'),
+ dict(name='project-test2', result='SUCCESS', changes='1,1'),
+ dict(name='project-merge', result='SUCCESS', changes='1,1 3,1'),
+ dict(name='project-test1', result='SUCCESS', changes='1,1 3,1'),
+ dict(name='project-test2', result='SUCCESS', changes='1,1 3,1'),
+ ], ordered=False)
+
+ def test_delayed_merge_conflict(self):
+ "Test that delayed check merge conflicts are handled properly"
+
+ # Hold jobs in the gearman queue so that we can test whether
+ # the executor returns a merge failure after the scheduler has
+ # successfully merged.
+ self.gearman_server.hold_jobs_in_queue = True
+ A = self.fake_gerrit.addFakeChange('org/project',
+ 'master', 'A',
+ files={'conflict': 'foo'})
+ B = self.fake_gerrit.addFakeChange('org/project',
+ 'master', 'B',
+ files={'conflict': 'bar'})
+ C = self.fake_gerrit.addFakeChange('org/project',
+ 'master', 'C')
+ C.setDependsOn(B, 1)
+
+ # A enters the gate queue; B and C enter the check queue
+ A.addApproval('code-review', 2)
+ self.fake_gerrit.addEvent(A.addApproval('approved', 1))
+ self.waitUntilSettled()
+ self.fake_gerrit.addEvent(B.getPatchsetCreatedEvent(1))
+ self.fake_gerrit.addEvent(C.getPatchsetCreatedEvent(1))
+ self.waitUntilSettled()
+
+ self.assertEqual(A.reported, 1)
+ self.assertEqual(B.reported, 0) # Check does not report start
+ self.assertEqual(C.reported, 0) # Check does not report start
+
+ # A merges while B and C are queued in check
+ # Release A project-merge
+ queue = self.gearman_server.getQueue()
+ self.release(queue[0])
+ self.waitUntilSettled()
+
+ # Release A project-test*
+ # gate has higher precedence, so A's test jobs are added in
+ # front of the merge jobs for B and C
+ queue = self.gearman_server.getQueue()
+ self.release(queue[0])
+ self.release(queue[1])
+ self.waitUntilSettled()
+
+ self.assertEqual(A.data['status'], 'MERGED')
+ self.assertEqual(B.data['status'], 'NEW')
+ self.assertEqual(C.data['status'], 'NEW')
+ self.assertEqual(A.reported, 2)
+ self.assertEqual(B.reported, 0)
+ self.assertEqual(C.reported, 0)
+ self.assertHistory([
+ dict(name='project-merge', result='SUCCESS', changes='1,1'),
+ dict(name='project-test1', result='SUCCESS', changes='1,1'),
+ dict(name='project-test2', result='SUCCESS', changes='1,1'),
+ ], ordered=False)
+
+ # B and C report merge conflicts
+ # Release B project-merge
+ queue = self.gearman_server.getQueue()
+ self.release(queue[0])
+ self.waitUntilSettled()
+
+ # Release C
+ self.gearman_server.hold_jobs_in_queue = False
+ self.gearman_server.release()
+ self.waitUntilSettled()
+
+ self.assertEqual(A.data['status'], 'MERGED')
+ self.assertEqual(B.data['status'], 'NEW')
+ self.assertEqual(C.data['status'], 'NEW')
+ self.assertEqual(A.reported, 2)
+ self.assertEqual(B.reported, 1)
+ self.assertEqual(C.reported, 1)
+
+ self.assertHistory([
+ dict(name='project-merge', result='SUCCESS', changes='1,1'),
+ dict(name='project-test1', result='SUCCESS', changes='1,1'),
+ dict(name='project-test2', result='SUCCESS', changes='1,1'),
+ dict(name='project-merge', result='MERGER_FAILURE', changes='2,1'),
+ dict(name='project-merge', result='MERGER_FAILURE',
+ changes='2,1 3,1'),
+ ], ordered=False)
def test_post(self):
"Test that post jobs run"
@@ -2719,7 +2806,6 @@
self.assertEqual(B.data['status'], 'MERGED')
self.assertEqual(B.reported, 2)
- @skip("Disabled for early v3 development")
def test_live_reconfiguration_del_project(self):
# Test project deletion from layout
# while changes are enqueued
@@ -2742,14 +2828,14 @@
self.assertEqual(len(self.builds), 5)
# This layout defines only org/project, not org/project1
- self.updateConfigLayout(
- 'tests/fixtures/layout-live-reconfiguration-del-project.yaml')
+ self.commitLayoutUpdate('common-config',
+ 'layout-live-reconfiguration-del-project')
self.sched.reconfigure(self.config)
self.waitUntilSettled()
# Builds for C aborted, builds for A succeed,
# and have change B applied ahead
- job_c = self.getJobFromHistory('project1-test1')
+ job_c = self.getJobFromHistory('project-test1')
self.assertEqual(job_c.changes, '3,1')
self.assertEqual(job_c.result, 'ABORTED')
@@ -2757,8 +2843,9 @@
self.executor_server.release()
self.waitUntilSettled()
- self.assertEqual(self.getJobFromHistory('project-test1').changes,
- '2,1 1,1')
+ self.assertEqual(
+ self.getJobFromHistory('project-test1', 'org/project').changes,
+ '2,1 1,1')
self.assertEqual(A.data['status'], 'NEW')
self.assertEqual(B.data['status'], 'NEW')
@@ -2767,40 +2854,11 @@
self.assertEqual(B.reported, 0)
self.assertEqual(C.reported, 0)
- self.assertEqual(len(self.sched.layout.pipelines['check'].queues), 0)
+ tenant = self.sched.abide.tenants.get('tenant-one')
+ self.assertEqual(len(tenant.layout.pipelines['check'].queues), 0)
self.assertIn('Build succeeded', A.messages[0])
@skip("Disabled for early v3 development")
- def test_live_reconfiguration_functions(self):
- "Test live reconfiguration with a custom function"
- self.worker.registerFunction('build:node-project-test1:debian')
- self.worker.registerFunction('build:node-project-test1:wheezy')
- A = self.fake_gerrit.addFakeChange('org/node-project', 'master', 'A')
- A.addApproval('code-review', 2)
- self.fake_gerrit.addEvent(A.addApproval('approved', 1))
- self.waitUntilSettled()
-
- self.assertIsNone(self.getJobFromHistory('node-project-merge').node)
- self.assertEqual(self.getJobFromHistory('node-project-test1').node,
- 'debian')
- self.assertIsNone(self.getJobFromHistory('node-project-test2').node)
-
- self.updateConfigLayout(
- 'tests/fixtures/layout-live-reconfiguration-functions.yaml')
- self.sched.reconfigure(self.config)
- self.worker.build_history = []
-
- B = self.fake_gerrit.addFakeChange('org/node-project', 'master', 'B')
- B.addApproval('code-review', 2)
- self.fake_gerrit.addEvent(B.addApproval('approved', 1))
- self.waitUntilSettled()
-
- self.assertIsNone(self.getJobFromHistory('node-project-merge').node)
- self.assertEqual(self.getJobFromHistory('node-project-test1').node,
- 'wheezy')
- self.assertIsNone(self.getJobFromHistory('node-project-test2').node)
-
- @skip("Disabled for early v3 development")
def test_delayed_repo_init(self):
self.updateConfigLayout(
'tests/fixtures/layout-delayed-repo-init.yaml')
@@ -3349,11 +3407,9 @@
self.executor_server.release()
self.waitUntilSettled()
- @skip("Disabled for early v3 development")
def test_queue_rate_limiting(self):
"Test that DependentPipelines are rate limited with dep across window"
- self.updateConfigLayout(
- 'tests/fixtures/layout-rate-limit.yaml')
+ self.updateConfigLayout('layout-rate-limit')
self.sched.reconfigure(self.config)
self.executor_server.hold_jobs_in_build = True
A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
@@ -3394,7 +3450,8 @@
self.executor_server.release('project-.*')
self.waitUntilSettled()
- queue = self.sched.layout.pipelines['gate'].queues[0]
+ tenant = self.sched.abide.tenants.get('openstack')
+ queue = tenant.layout.pipelines['gate'].queues[0]
# A failed so window is reduced by 1 to 1.
self.assertEqual(queue.window, 1)
self.assertEqual(queue.window_floor, 1)
@@ -3441,11 +3498,9 @@
self.assertEqual(queue.window_floor, 1)
self.assertEqual(C.data['status'], 'MERGED')
- @skip("Disabled for early v3 development")
def test_queue_rate_limiting_dependent(self):
"Test that DependentPipelines are rate limited with dep in window"
- self.updateConfigLayout(
- 'tests/fixtures/layout-rate-limit.yaml')
+ self.updateConfigLayout('layout-rate-limit')
self.sched.reconfigure(self.config)
self.executor_server.hold_jobs_in_build = True
A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
@@ -3487,7 +3542,8 @@
self.executor_server.release('project-.*')
self.waitUntilSettled()
- queue = self.sched.layout.pipelines['gate'].queues[0]
+ tenant = self.sched.abide.tenants.get('openstack')
+ queue = tenant.layout.pipelines['gate'].queues[0]
# A failed so window is reduced by 1 to 1.
self.assertEqual(queue.window, 1)
self.assertEqual(queue.window_floor, 1)
@@ -4233,13 +4289,10 @@
self.init_repo("org/unknown")
self._test_crd_check_reconfiguration('org/project1', 'org/unknown')
- @skip("Disabled for early v3 development")
def test_crd_check_ignore_dependencies(self):
"Test cross-repo dependencies can be ignored"
- self.updateConfigLayout(
- 'tests/fixtures/layout-ignore-dependencies.yaml')
+ self.updateConfigLayout('layout-ignore-dependencies')
self.sched.reconfigure(self.config)
- self.registerJobs()
self.gearman_server.hold_jobs_in_queue = True
A = self.fake_gerrit.addFakeChange('org/project1', 'master', 'A')
@@ -4258,7 +4311,8 @@
# Make sure none of the items share a change queue, and all
# are live.
- check_pipeline = self.sched.layout.pipelines['check']
+ tenant = self.sched.abide.tenants.get('openstack')
+ check_pipeline = tenant.layout.pipelines['check']
self.assertEqual(len(check_pipeline.queues), 3)
self.assertEqual(len(check_pipeline.getAllItems()), 3)
for item in check_pipeline.getAllItems():
@@ -4279,7 +4333,6 @@
for job in self.history:
self.assertEqual(len(job.changes.split()), 1)
- @skip("Disabled for early v3 development")
def test_crd_check_transitive(self):
"Test transitive cross-repo dependencies"
# Specifically, if A -> B -> C, and C gets a new patchset and
diff --git a/tools/test-setup.sh b/tools/test-setup.sh
index f4a0458..3bdedf5 100755
--- a/tools/test-setup.sh
+++ b/tools/test-setup.sh
@@ -6,6 +6,10 @@
# This setup needs to be run as a user that can run sudo.
+# Be sure mysql and zookeeper are started.
+sudo service mysql start
+sudo service zookeeper start
+
# The root password for the MySQL database; pass it in via
# MYSQL_ROOT_PW.
DB_ROOT_PW=${MYSQL_ROOT_PW:-insecure_slave}
diff --git a/zuul/ansible/lookup/__init__.py b/zuul/ansible/lookup/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/zuul/ansible/lookup/__init__.py
diff --git a/zuul/ansible/lookup/_banned.py b/zuul/ansible/lookup/_banned.py
new file mode 100644
index 0000000..65708f8
--- /dev/null
+++ b/zuul/ansible/lookup/_banned.py
@@ -0,0 +1,25 @@
+# Copyright 2017 Red Hat, Inc.
+#
+# This module is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This software is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this software. If not, see <http://www.gnu.org/licenses/>.
+
+from ansible.errors import AnsibleError
+from ansible.plugins.lookup import LookupBase
+
+
+class LookupModule(LookupBase):
+
+ def run(self, *args, **kwargs):
+ raise AnsibleError(
+ "Use of lookup modules that perform local actions on the executor"
+ " is forbidden.")
diff --git a/zuul/ansible/lookup/consul_kv.py b/zuul/ansible/lookup/consul_kv.py
new file mode 120000
index 0000000..d45b9c4
--- /dev/null
+++ b/zuul/ansible/lookup/consul_kv.py
@@ -0,0 +1 @@
+_banned.py
\ No newline at end of file
diff --git a/zuul/ansible/lookup/credstash.py b/zuul/ansible/lookup/credstash.py
new file mode 120000
index 0000000..d45b9c4
--- /dev/null
+++ b/zuul/ansible/lookup/credstash.py
@@ -0,0 +1 @@
+_banned.py
\ No newline at end of file
diff --git a/zuul/ansible/lookup/csvfile.py b/zuul/ansible/lookup/csvfile.py
new file mode 100644
index 0000000..6506aa2
--- /dev/null
+++ b/zuul/ansible/lookup/csvfile.py
@@ -0,0 +1,25 @@
+# Copyright 2017 Red Hat, Inc.
+#
+# This module is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This software is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this software. If not, see <http://www.gnu.org/licenses/>.
+
+
+from zuul.ansible import paths
+csvfile = paths._import_ansible_lookup_plugin("csvfile")
+
+
+class LookupModule(csvfile.LookupModule):
+
+ def read_csv(self, filename, *args, **kwargs):
+ paths._fail_if_unsafe(filename)
+ return super(LookupModule, self).read_csv(filename, *args, **kwargs)
diff --git a/zuul/ansible/lookup/dig.py b/zuul/ansible/lookup/dig.py
new file mode 120000
index 0000000..d45b9c4
--- /dev/null
+++ b/zuul/ansible/lookup/dig.py
@@ -0,0 +1 @@
+_banned.py
\ No newline at end of file
diff --git a/zuul/ansible/lookup/dnstxt.py b/zuul/ansible/lookup/dnstxt.py
new file mode 120000
index 0000000..d45b9c4
--- /dev/null
+++ b/zuul/ansible/lookup/dnstxt.py
@@ -0,0 +1 @@
+_banned.py
\ No newline at end of file
diff --git a/zuul/ansible/lookup/env.py b/zuul/ansible/lookup/env.py
new file mode 120000
index 0000000..d45b9c4
--- /dev/null
+++ b/zuul/ansible/lookup/env.py
@@ -0,0 +1 @@
+_banned.py
\ No newline at end of file
diff --git a/zuul/ansible/lookup/etcd.py b/zuul/ansible/lookup/etcd.py
new file mode 120000
index 0000000..d45b9c4
--- /dev/null
+++ b/zuul/ansible/lookup/etcd.py
@@ -0,0 +1 @@
+_banned.py
\ No newline at end of file
diff --git a/zuul/ansible/lookup/file.py b/zuul/ansible/lookup/file.py
new file mode 100644
index 0000000..7403535
--- /dev/null
+++ b/zuul/ansible/lookup/file.py
@@ -0,0 +1,28 @@
+# Copyright 2017 Red Hat, Inc.
+#
+# This module is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This software is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this software. If not, see <http://www.gnu.org/licenses/>.
+
+
+from zuul.ansible import paths
+file_mod = paths._import_ansible_lookup_plugin("file")
+
+
+class LookupModule(file_mod.LookupModule):
+
+ def run(self, terms, variables=None, **kwargs):
+ for term in terms:
+ lookupfile = self.find_file_in_search_path(
+ variables, 'files', term)
+ paths._fail_if_unsafe(lookupfile)
+ return super(LookupModule, self).run(terms, variables, **kwargs)
diff --git a/zuul/ansible/lookup/fileglob.py b/zuul/ansible/lookup/fileglob.py
new file mode 100644
index 0000000..4b9b449
--- /dev/null
+++ b/zuul/ansible/lookup/fileglob.py
@@ -0,0 +1,45 @@
+# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
+# Copyright 2017 Red Hat, Inc.
+#
+# This module is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This software is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this software. If not, see <http://www.gnu.org/licenses/>.
+
+# Forked from lib/ansible/plugins/lookup/fileglob.py in ansible
+
+import os
+import glob
+
+from zuul.ansible import paths
+
+from ansible.plugins.lookup import LookupBase
+from ansible.module_utils._text import to_bytes, to_text
+
+
+class LookupModule(LookupBase):
+
+ def run(self, terms, variables=None, **kwargs):
+
+ ret = []
+ for term in terms:
+ term_file = os.path.basename(term)
+ dwimmed_path = self.find_file_in_search_path(
+ variables, 'files', os.path.dirname(term))
+ if dwimmed_path:
+ paths._fail_if_unsafe(dwimmed_path)
+ globbed = glob.glob(to_bytes(
+ os.path.join(dwimmed_path, term_file),
+ errors='surrogate_or_strict'))
+ ret.extend(
+ to_text(g, errors='surrogate_or_strict')
+ for g in globbed if os.path.isfile(g))
+ return ret
diff --git a/zuul/ansible/lookup/filetree.py b/zuul/ansible/lookup/filetree.py
new file mode 100644
index 0000000..0c054a3
--- /dev/null
+++ b/zuul/ansible/lookup/filetree.py
@@ -0,0 +1,32 @@
+# Copyright 2017 Red Hat, Inc.
+#
+# This module is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This software is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this software. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+
+from zuul.ansible import paths
+filetree = paths._import_ansible_lookup_plugin("filetree")
+
+
+class LookupModule(filetree.LookupModule):
+
+ def run(self, terms, variables=None, **kwargs):
+ basedir = self.get_basedir(variables)
+ for term in terms:
+ term_file = os.path.basename(term)
+ dwimmed_path = self._loader.path_dwim_relative(
+ basedir, 'files', os.path.dirname(term))
+ path = os.path.join(dwimmed_path, term_file)
+ paths._fail_if_unsafe(path)
+ return super(LookupModule, self).run(terms, variables, **kwargs)
diff --git a/zuul/ansible/lookup/first_found.py b/zuul/ansible/lookup/first_found.py
new file mode 100644
index 0000000..d741df0
--- /dev/null
+++ b/zuul/ansible/lookup/first_found.py
@@ -0,0 +1,201 @@
+# (c) 2013, seth vidal <skvidal@fedoraproject.org> red hat, inc
+# Copyright 2017 Red Hat, Inc.
+#
+# This module is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This software is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this software. If not, see <http://www.gnu.org/licenses/>.
+
+# take a list of files and (optionally) a list of paths
+# return the first existing file found in the paths
+# [file1, file2, file3], [path1, path2, path3]
+# search order is:
+# path1/file1
+# path1/file2
+# path1/file3
+# path2/file1
+# path2/file2
+# path2/file3
+# path3/file1
+# path3/file2
+# path3/file3
+
+# first file found with os.path.exists() is returned
+# no file matches raises ansibleerror
+# EXAMPLES
+# - name: copy first existing file found to /some/file
+# action: copy src=$item dest=/some/file
+# with_first_found:
+# - files: foo ${inventory_hostname} bar
+# paths: /tmp/production /tmp/staging
+
+# that will look for files in this order:
+# /tmp/production/foo
+# ${inventory_hostname}
+# bar
+# /tmp/staging/foo
+# ${inventory_hostname}
+# bar
+
+# - name: copy first existing file found to /some/file
+# action: copy src=$item dest=/some/file
+# with_first_found:
+# - files: /some/place/foo ${inventory_hostname} /some/place/else
+
+# that will look for files in this order:
+# /some/place/foo
+# $relative_path/${inventory_hostname}
+# /some/place/else
+
+# example - including tasks:
+# tasks:
+# - include: $item
+# with_first_found:
+# - files: generic
+# paths: tasks/staging tasks/production
+# this will include the tasks in the file generic where it is found first
+# (staging or production)
+
+# example simple file lists
+# tasks:
+# - name: first found file
+# action: copy src=$item dest=/etc/file.cfg
+# with_first_found:
+# - files: foo.${inventory_hostname} foo
+
+
+# example skipping if no matched files
+# First_found also offers the ability to control whether or not failing
+# to find a file returns an error or not
+#
+# - name: first found file - or skip
+# action: copy src=$item dest=/etc/file.cfg
+# with_first_found:
+# - files: foo.${inventory_hostname}
+# skip: true
+
+# example a role with default configuration and configuration per host
+# you can set multiple terms with their own files and paths to look through.
+# consider a role that sets some configuration per host falling back on a
+# default config.
+#
+# - name: some configuration template
+# template: src={{ item }} dest=/etc/file.cfg mode=0444 owner=root group=root
+# with_first_found:
+# - files:
+# - ${inventory_hostname}/etc/file.cfg
+# paths:
+# - ../../../templates.overwrites
+# - ../../../templates
+# - files:
+# - etc/file.cfg
+# paths:
+# - templates
+
+# the above will return an empty list if the files cannot be found at all
+# if skip is unspecificed or if it is set to false then it will return a list
+# error which can be caught bye ignore_errors: true for that action.
+
+# finally - if you want you can use it, in place to replace
+# first_available_file:
+# you simply cannot use the - files, path or skip options. simply replace
+# first_available_file with with_first_found and leave the file listing in
+# place
+#
+#
+# - name: with_first_found like first_available_file
+# action: copy src=$item dest=/tmp/faftest
+# with_first_found:
+# - ../files/foo
+# - ../files/bar
+# - ../files/baz
+# ignore_errors: true
+
+import os
+
+from jinja2.exceptions import UndefinedError
+
+from ansible.constants import mk_boolean as boolean
+from ansible.errors import AnsibleLookupError
+from ansible.errors import AnsibleUndefinedVariable
+from ansible.module_utils.six import string_types
+from ansible.plugins.lookup import LookupBase
+
+from zuul.ansible import paths as zuul_paths
+
+
+class LookupModule(LookupBase):
+
+ def run(self, terms, variables, **kwargs):
+
+ anydict = False
+ skip = False
+
+ for term in terms:
+ if isinstance(term, dict):
+ anydict = True
+
+ total_search = []
+ if anydict:
+ for term in terms:
+ if isinstance(term, dict):
+ files = term.get('files', [])
+ paths = term.get('paths', [])
+ skip = boolean(term.get('skip', False))
+
+ filelist = files
+ if isinstance(files, string_types):
+ files = files.replace(',', ' ')
+ files = files.replace(';', ' ')
+ filelist = files.split(' ')
+
+ pathlist = paths
+ if paths:
+ if isinstance(paths, string_types):
+ paths = paths.replace(',', ' ')
+ paths = paths.replace(':', ' ')
+ paths = paths.replace(';', ' ')
+ pathlist = paths.split(' ')
+
+ if not pathlist:
+ total_search = filelist
+ else:
+ for path in pathlist:
+ for fn in filelist:
+ f = os.path.join(path, fn)
+ total_search.append(f)
+ else:
+ total_search.append(term)
+ else:
+ total_search = self._flatten(terms)
+
+ for fn in total_search:
+ zuul_paths._fail_if_unsafe(fn)
+ try:
+ fn = self._templar.template(fn)
+ except (AnsibleUndefinedVariable, UndefinedError):
+ continue
+
+ # get subdir if set by task executor, default to files otherwise
+ subdir = getattr(self, '_subdir', 'files')
+ path = None
+ path = self.find_file_in_search_path(
+ variables, subdir, fn, ignore_missing=True)
+ if path is not None:
+ return [path]
+ else:
+ if skip:
+ return []
+ else:
+ raise AnsibleLookupError(
+ "No file was found when using with_first_found. Use the"
+ " 'skip: true' option to allow this task to be skipped if"
+ " no files are found")
diff --git a/zuul/ansible/lookup/hashi_valut.py b/zuul/ansible/lookup/hashi_valut.py
new file mode 120000
index 0000000..d45b9c4
--- /dev/null
+++ b/zuul/ansible/lookup/hashi_valut.py
@@ -0,0 +1 @@
+_banned.py
\ No newline at end of file
diff --git a/zuul/ansible/lookup/ini.py b/zuul/ansible/lookup/ini.py
new file mode 100644
index 0000000..51127ff
--- /dev/null
+++ b/zuul/ansible/lookup/ini.py
@@ -0,0 +1,31 @@
+# Copyright 2017 Red Hat, Inc.
+#
+# This module is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This software is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this software. If not, see <http://www.gnu.org/licenses/>.
+
+
+from zuul.ansible import paths
+ini = paths._import_ansible_lookup_plugin("ini")
+
+
+class LookupModule(ini.LookupModule):
+
+ def read_properties(self, filename, *args, **kwargs):
+ paths._fail_if_unsafe(filename)
+ return super(LookupModule, self).read_properties(
+ filename, *args, **kwargs)
+
+ def read_ini(self, filename, *args, **kwargs):
+ paths._fail_if_unsafe(filename)
+ return super(LookupModule, self).read_ini(
+ filename, *args, **kwargs)
diff --git a/zuul/ansible/lookup/keyring.py b/zuul/ansible/lookup/keyring.py
new file mode 120000
index 0000000..d45b9c4
--- /dev/null
+++ b/zuul/ansible/lookup/keyring.py
@@ -0,0 +1 @@
+_banned.py
\ No newline at end of file
diff --git a/zuul/ansible/lookup/lastpass.py b/zuul/ansible/lookup/lastpass.py
new file mode 120000
index 0000000..d45b9c4
--- /dev/null
+++ b/zuul/ansible/lookup/lastpass.py
@@ -0,0 +1 @@
+_banned.py
\ No newline at end of file
diff --git a/zuul/ansible/lookup/lines.py b/zuul/ansible/lookup/lines.py
new file mode 120000
index 0000000..d45b9c4
--- /dev/null
+++ b/zuul/ansible/lookup/lines.py
@@ -0,0 +1 @@
+_banned.py
\ No newline at end of file
diff --git a/zuul/ansible/lookup/mongodb.py b/zuul/ansible/lookup/mongodb.py
new file mode 120000
index 0000000..d45b9c4
--- /dev/null
+++ b/zuul/ansible/lookup/mongodb.py
@@ -0,0 +1 @@
+_banned.py
\ No newline at end of file
diff --git a/zuul/ansible/lookup/password.py b/zuul/ansible/lookup/password.py
new file mode 120000
index 0000000..d45b9c4
--- /dev/null
+++ b/zuul/ansible/lookup/password.py
@@ -0,0 +1 @@
+_banned.py
\ No newline at end of file
diff --git a/zuul/ansible/lookup/passwordstore.py b/zuul/ansible/lookup/passwordstore.py
new file mode 120000
index 0000000..d45b9c4
--- /dev/null
+++ b/zuul/ansible/lookup/passwordstore.py
@@ -0,0 +1 @@
+_banned.py
\ No newline at end of file
diff --git a/zuul/ansible/lookup/pipe.py b/zuul/ansible/lookup/pipe.py
new file mode 120000
index 0000000..d45b9c4
--- /dev/null
+++ b/zuul/ansible/lookup/pipe.py
@@ -0,0 +1 @@
+_banned.py
\ No newline at end of file
diff --git a/zuul/ansible/lookup/redis_kv.py b/zuul/ansible/lookup/redis_kv.py
new file mode 120000
index 0000000..d45b9c4
--- /dev/null
+++ b/zuul/ansible/lookup/redis_kv.py
@@ -0,0 +1 @@
+_banned.py
\ No newline at end of file
diff --git a/zuul/ansible/lookup/shelvefile.py b/zuul/ansible/lookup/shelvefile.py
new file mode 120000
index 0000000..d45b9c4
--- /dev/null
+++ b/zuul/ansible/lookup/shelvefile.py
@@ -0,0 +1 @@
+_banned.py
\ No newline at end of file
diff --git a/zuul/ansible/lookup/template.py b/zuul/ansible/lookup/template.py
new file mode 120000
index 0000000..d45b9c4
--- /dev/null
+++ b/zuul/ansible/lookup/template.py
@@ -0,0 +1 @@
+_banned.py
\ No newline at end of file
diff --git a/zuul/ansible/lookup/url.py b/zuul/ansible/lookup/url.py
new file mode 120000
index 0000000..d45b9c4
--- /dev/null
+++ b/zuul/ansible/lookup/url.py
@@ -0,0 +1 @@
+_banned.py
\ No newline at end of file
diff --git a/zuul/ansible/paths.py b/zuul/ansible/paths.py
index e387732..bc61975 100644
--- a/zuul/ansible/paths.py
+++ b/zuul/ansible/paths.py
@@ -16,7 +16,9 @@
import imp
import os
+from ansible.errors import AnsibleError
import ansible.plugins.action
+import ansible.plugins.lookup
def _is_safe_path(path):
@@ -35,6 +37,12 @@
curdir=os.path.abspath(os.path.curdir)))
+def _fail_if_unsafe(path):
+ if not _is_safe_path(path):
+ msg_dict = _fail_dict(path)
+ raise AnsibleError(msg_dict['msg'])
+
+
def _import_ansible_action_plugin(name):
# Ansible forces the import of our action plugins
# (zuul.ansible.action.foo) as ansible.plugins.action.foo, which
@@ -51,3 +59,11 @@
return imp.load_module(
'zuul.ansible.protected.action.' + name,
*imp.find_module(name, ansible.plugins.action.__path__))
+
+
+def _import_ansible_lookup_plugin(name):
+ # See _import_ansible_action_plugin
+
+ return imp.load_module(
+ 'zuul.ansible.protected.lookup.' + name,
+ *imp.find_module(name, ansible.plugins.lookup.__path__))
diff --git a/zuul/cmd/__init__.py b/zuul/cmd/__init__.py
index 9fa4c03..f2a2612 100644
--- a/zuul/cmd/__init__.py
+++ b/zuul/cmd/__init__.py
@@ -24,10 +24,10 @@
import sys
import traceback
-import yaml
yappi = extras.try_import('yappi')
import zuul.lib.connections
+from zuul.lib import yamlutil as yaml
# Do not import modules that will pull in paramiko which must not be
# imported until after the daemonization.
diff --git a/zuul/configloader.py b/zuul/configloader.py
index ecba760..5e88ee7 100644
--- a/zuul/configloader.py
+++ b/zuul/configloader.py
@@ -15,13 +15,13 @@
import os
import logging
import six
-import yaml
import pprint
import textwrap
import voluptuous as vs
from zuul import model
+from zuul.lib import yamlutil as yaml
import zuul.manager.dependent
import zuul.manager.independent
from zuul import change_matcher
diff --git a/zuul/executor/ansiblelaunchserver.py b/zuul/executor/ansiblelaunchserver.py
index 875cf2b..0202bdd 100644
--- a/zuul/executor/ansiblelaunchserver.py
+++ b/zuul/executor/ansiblelaunchserver.py
@@ -35,13 +35,13 @@
import Queue
import gear
-import yaml
import jenkins_jobs.builder
import jenkins_jobs.formatter
import zmq
import zuul.ansible.library
from zuul.lib import commandsocket
+from zuul.lib import yamlutil as yaml
ANSIBLE_WATCHDOG_GRACE = 5 * 60
ANSIBLE_DEFAULT_TIMEOUT = 2 * 60 * 60
diff --git a/zuul/executor/server.py b/zuul/executor/server.py
index 027c63c..0adb6de 100644
--- a/zuul/executor/server.py
+++ b/zuul/executor/server.py
@@ -24,15 +24,17 @@
import threading
import time
import traceback
-import yaml
+from zuul.lib.yamlutil import yaml
import gear
import git
+from six.moves import shlex_quote
import zuul.merger.merger
import zuul.ansible.action
import zuul.ansible.callback
import zuul.ansible.library
+import zuul.ansible.lookup
from zuul.lib import commandsocket
COMMANDS = ['stop', 'pause', 'unpause', 'graceful', 'verbose',
@@ -274,6 +276,10 @@
if not os.path.exists(self.callback_dir):
os.makedirs(self.callback_dir)
+ self.lookup_dir = os.path.join(ansible_dir, 'lookup')
+ if not os.path.exists(self.lookup_dir):
+ os.makedirs(self.lookup_dir)
+
library_path = os.path.dirname(os.path.abspath(
zuul.ansible.library.__file__))
for fn in os.listdir(library_path):
@@ -289,6 +295,11 @@
for fn in os.listdir(callback_path):
shutil.copy(os.path.join(callback_path, fn), self.callback_dir)
+ lookup_path = os.path.dirname(os.path.abspath(
+ zuul.ansible.lookup.__file__))
+ for fn in os.listdir(lookup_path):
+ shutil.copy(os.path.join(lookup_path, fn), self.lookup_dir)
+
self.job_workers = {}
def _getMerger(self, root):
@@ -467,7 +478,10 @@
result = dict(merged=(ret is not None),
zuul_url=self.zuul_url)
if args.get('files'):
- result['commit'], result['files'] = ret
+ if ret:
+ result['commit'], result['files'] = ret
+ else:
+ result['commit'], result['files'] = (None, None)
else:
result['commit'] = ret
job.sendWorkComplete(json.dumps(result))
@@ -552,11 +566,13 @@
project['name']))
repo.remotes.origin.config_writer.set('url', project['url'])
- # Get a merger in order to update the repos involved in this job.
- merger = self.executor_server._getMerger(self.jobdir.src_root)
merge_items = [i for i in args['items'] if i.get('refspec')]
if merge_items:
- commit = merger.mergeChanges(merge_items) # noqa
+ commit = self.doMergeChanges(merge_items)
+ if not commit:
+ # There was a merge conflict and we have already sent
+ # a work complete result, don't run any jobs
+ return
else:
commit = args['items'][-1]['newrev'] # noqa
@@ -596,6 +612,15 @@
result = dict(result=result)
self.job.sendWorkComplete(json.dumps(result))
+ def doMergeChanges(self, items):
+ # Get a merger in order to update the repos involved in this job.
+ merger = self.executor_server._getMerger(self.jobdir.src_root)
+ commit = merger.mergeChanges(items) # noqa
+ if not commit: # merge conflict
+ result = dict(result='MERGER_FAILURE')
+ self.job.sendWorkComplete(json.dumps(result))
+ return commit
+
def runPlaybooks(self, args):
result = None
@@ -871,6 +896,8 @@
if not trusted:
config.write('action_plugins = %s\n'
% self.executor_server.action_dir)
+ config.write('lookup_plugins = %s\n'
+ % self.executor_server.lookup_dir)
# On trusted jobs, we want to prevent the printing of args,
# since trusted jobs might have access to secrets that they may
@@ -916,14 +943,17 @@
env_copy['LOGNAME'] = 'zuul'
if trusted:
- env_copy['ANSIBLE_CONFIG'] = self.jobdir.trusted_config
+ config_file = self.jobdir.trusted_config
else:
- env_copy['ANSIBLE_CONFIG'] = self.jobdir.untrusted_config
+ config_file = self.jobdir.untrusted_config
+
+ env_copy['ANSIBLE_CONFIG'] = config_file
with self.proc_lock:
if self.aborted:
return (self.RESULT_ABORTED, None)
- self.log.debug("Ansible command: %s" % (cmd,))
+ self.log.debug("Ansible command: ANSIBLE_CONFIG=%s %s",
+ config_file, " ".join(shlex_quote(c) for c in cmd))
self.proc = subprocess.Popen(
cmd,
cwd=self.jobdir.work_root,
diff --git a/zuul/lib/cloner.py b/zuul/lib/cloner.py
index 18dea91..bec8ebe 100644
--- a/zuul/lib/cloner.py
+++ b/zuul/lib/cloner.py
@@ -17,13 +17,13 @@
import logging
import os
import re
-import yaml
import six
from git import GitCommandError
from zuul import exceptions
from zuul.lib.clonemapper import CloneMapper
+from zuul.lib import yamlutil as yaml
from zuul.merger.merger import Repo
diff --git a/zuul/lib/yamlutil.py b/zuul/lib/yamlutil.py
new file mode 100644
index 0000000..2419906
--- /dev/null
+++ b/zuul/lib/yamlutil.py
@@ -0,0 +1,32 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+import yaml
+from yaml import YAMLObject, YAMLError # noqa: F401
+
+try:
+ from yaml import cyaml
+ import _yaml
+ SafeLoader = cyaml.CSafeLoader
+ SafeDumper = cyaml.CSafeDumper
+ Mark = _yaml.Mark
+except ImportError:
+ SafeLoader = yaml.SafeLoader
+ SafeDumper = yaml.SafeDumper
+ Mark = yaml.Mark
+
+
+def safe_load(stream, *args, **kwargs):
+ return yaml.load(stream, *args, Loader=SafeLoader, **kwargs)
+
+
+def safe_dump(stream, *args, **kwargs):
+ return yaml.dump(stream, *args, Dumper=SafeDumper, **kwargs)
diff --git a/zuul/manager/__init__.py b/zuul/manager/__init__.py
index 75e8edb..9507d15 100644
--- a/zuul/manager/__init__.py
+++ b/zuul/manager/__init__.py
@@ -442,11 +442,15 @@
# the merger.
number = None
patchset = None
+ refspec = None
+ branch = None
oldrev = None
newrev = None
if hasattr(item.change, 'number'):
number = item.change.number
patchset = item.change.patchset
+ refspec = item.change.refspec
+ branch = item.change.branch
elif hasattr(item.change, 'newrev'):
oldrev = item.change.oldrev
newrev = item.change.newrev
@@ -458,8 +462,8 @@
item.change.project),
connection_name=connection_name,
merge_mode=item.current_build_set.getMergeMode(project),
- refspec=item.change.refspec,
- branch=item.change.branch,
+ refspec=refspec,
+ branch=branch,
ref=item.current_build_set.ref,
number=number,
patchset=patchset,
@@ -517,30 +521,54 @@
if build_set.merge_state == build_set.COMPLETE:
if build_set.unable_to_merge:
return None
+ self.log.debug("Preparing dynamic layout for: %s" % item.change)
return self._loadDynamicLayout(item)
- build_set.merge_state = build_set.PENDING
- self.log.debug("Preparing dynamic layout for: %s" % item.change)
+
+ def scheduleMerge(self, item, files=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)" %
+ (item, files))
dependent_items = self.getDependentItems(item)
dependent_items.reverse()
all_items = dependent_items + [item]
merger_items = map(self._makeMergerItem, all_items)
+ build_set = item.current_build_set
+ build_set.merge_state = build_set.PENDING
self.sched.merger.mergeChanges(merger_items,
item.current_build_set,
- ['zuul.yaml', '.zuul.yaml'],
+ files,
self.pipeline.precedence)
+ return False
- def prepareLayout(self, item):
- # Get a copy of the layout in the context of the current
- # queue.
- # Returns True if the ref is ready, false otherwise
- if not item.current_build_set.ref:
- item.current_build_set.setConfiguration()
- if not item.current_build_set.layout:
- item.current_build_set.layout = self.getLayout(item)
- if not item.current_build_set.layout:
+ def prepareItem(self, item):
+ # This runs on every iteration of _processOneItem
+ # Returns True if the item is ready, false otherwise
+ build_set = item.current_build_set
+ if not build_set.ref:
+ build_set.setConfiguration()
+ if build_set.merge_state == build_set.NEW:
+ return self.scheduleMerge(item, ['zuul.yaml', '.zuul.yaml'])
+ if build_set.config_error:
return False
- if item.current_build_set.config_error:
+ return True
+
+ def prepareJobs(self, item):
+ # This only runs once the item is in the pipeline's action window
+ # Returns True if the item is ready, false otherwise
+ build_set = item.current_build_set
+ if not build_set.layout:
+ build_set.layout = self.getLayout(item)
+ if not build_set.layout:
return False
+
if not item.job_graph:
try:
item.freezeJobGraph()
@@ -555,11 +583,13 @@
def _processOneItem(self, item, nnfi):
changed = False
+ ready = False
+ failing_reasons = [] # Reasons this item is failing
+
item_ahead = item.item_ahead
if item_ahead and (not item_ahead.live):
item_ahead = None
change_queue = item.queue
- failing_reasons = [] # Reasons this item is failing
if self.checkForChangesNeededBy(item.change, change_queue) is not True:
# It's not okay to enqueue this change, we should remove it.
@@ -574,10 +604,11 @@
except exceptions.MergeFailure:
pass
return (True, nnfi)
- dep_items = self.getFailingDependentItems(item)
+
actionable = change_queue.isActionable(item)
item.active = actionable
- ready = False
+
+ dep_items = self.getFailingDependentItems(item)
if dep_items:
failing_reasons.append('a needed change is failing')
self.cancelJobs(item, prime=False)
@@ -596,15 +627,16 @@
changed = True
self.cancelJobs(item)
if actionable:
- ready = self.prepareLayout(item)
+ ready = self.prepareItem(item) and self.prepareJobs(item)
if item.current_build_set.unable_to_merge:
failing_reasons.append("it has a merge conflict")
if item.current_build_set.config_error:
failing_reasons.append("it has an invalid configuration")
if ready and self.provisionNodes(item):
changed = True
- if actionable and ready and self.executeJobs(item):
+ if ready and self.executeJobs(item):
changed = True
+
if item.didAnyJobFail():
failing_reasons.append("at least one job failed")
if (not item.live) and (not item.items_behind):
@@ -742,10 +774,11 @@
# TODOv3(jeblair): consider a new reporter action for this
actions = self.pipeline.merge_failure_actions
item.setReportedResult('CONFIG_ERROR')
+ elif item.didMergerFail():
+ actions = self.pipeline.merge_failure_actions
+ item.setReportedResult('MERGER_FAILURE')
elif not item.getJobs():
- # We don't send empty reports with +1,
- # and the same for -1's (merge failures or transient errors)
- # as they cannot be followed by +1's
+ # We don't send empty reports with +1
self.log.debug("No jobs for change %s" % item.change)
actions = []
elif item.didAllJobsSucceed():
@@ -753,9 +786,6 @@
actions = self.pipeline.success_actions
item.setReportedResult('SUCCESS')
self.pipeline._consecutive_failures = 0
- elif item.didMergerFail():
- actions = self.pipeline.merge_failure_actions
- item.setReportedResult('MERGER_FAILURE')
else:
actions = self.pipeline.failure_actions
item.setReportedResult('FAILURE')
diff --git a/zuul/merger/server.py b/zuul/merger/server.py
index c2738a2..540105e 100644
--- a/zuul/merger/server.py
+++ b/zuul/merger/server.py
@@ -111,7 +111,10 @@
result = dict(merged=(ret is not None),
zuul_url=self.zuul_url)
if args.get('files'):
- result['commit'], result['files'] = ret
+ if ret:
+ result['commit'], result['files'] = ret
+ else:
+ result['commit'], result['files'] = (None, None)
else:
result['commit'] = ret
job.sendWorkComplete(json.dumps(result))