Handle nodesets in branches

This allows a nodeset to appear on multiple branches of a project.

See Ia9d5b77d1ce46e6461b370e951301ede4045bbb9 for more information.

Change-Id: If7ca83e8ec3cad8f9bd99a65b974d56a999f256c
This commit is contained in:
James E. Blair 2018-01-17 15:49:59 -08:00
parent a17a8e7ba4
commit 8446c415c8
12 changed files with 260 additions and 5 deletions

View File

@ -1213,6 +1213,12 @@ specify what nodes they require individually, however, by defining
groups of node types once and referring to them by name, job
configuration may be simplified.
Nodesets, like most configuration items, are globally unique, though a
nodeset may be defined on multiple branches of the same project as long
as the contents are the same. This is to aid in branch maintenance,
so that creating a new branch based on an existing branch will not
immediately produce a configuration error.
.. code-block:: yaml
- nodeset:

View File

@ -0,0 +1,2 @@
- hosts: all
tasks: []

View File

@ -0,0 +1,39 @@
- pipeline:
name: check
manager: independent
post-review: true
trigger:
gerrit:
- event: patchset-created
success:
gerrit:
Verified: 1
failure:
gerrit:
Verified: -1
- pipeline:
name: gate
manager: dependent
success-message: Build succeeded (gate).
trigger:
gerrit:
- event: comment-added
approval:
- Approved: 1
success:
gerrit:
Verified: 2
submit: true
failure:
gerrit:
Verified: -2
start:
gerrit:
Verified: 0
precedence: high
- job:
name: base
parent: null
run: playbooks/base.yaml

View File

@ -0,0 +1 @@
test

View File

@ -0,0 +1,15 @@
- nodeset:
name: project1-nodeset
nodes:
- name: controller
label: ubuntu-xenial
- job:
parent: base
name: project1-test
nodeset: project1-nodeset
- project:
check:
jobs:
- project1-test

View File

@ -0,0 +1 @@
test

View File

@ -0,0 +1,18 @@
- nodeset:
name: project2-nodeset
nodes:
name: controller
label: ubuntu-xenial
- job:
parent: base
name: project2-test
nodeset: project2-nodeset
- project:
check:
jobs:
- project2-test
gate:
jobs:
- noop

View File

@ -0,0 +1,11 @@
- job:
parent: base
name: project2-test
- project:
check:
jobs:
- project2-test
gate:
jobs:
- noop

View File

@ -0,0 +1,9 @@
- tenant:
name: tenant-one
source:
gerrit:
config-projects:
- common-config
untrusted-projects:
- org/project1
- org/project2

View File

@ -2875,6 +2875,143 @@ class TestSecretLeaks(AnsibleZuulTestCase):
self._test_secret_file_fail()
class TestNodesets(ZuulTestCase):
tenant_config_file = 'config/nodesets/main.yaml'
def test_nodeset_branch(self):
# Test that we can use a nodeset defined in another branch of
# the same project.
self.create_branch('org/project2', 'stable')
self.fake_gerrit.addEvent(
self.fake_gerrit.getFakeBranchCreatedEvent(
'org/project2', 'stable'))
self.waitUntilSettled()
with open(os.path.join(FIXTURE_DIR,
'config/nodesets/git/',
'org_project2/zuul-nodeset.yaml')) as f:
config = f.read()
file_dict = {'zuul.yaml': config}
A = self.fake_gerrit.addFakeChange('org/project2', 'master', 'A',
files=file_dict)
A.addApproval('Code-Review', 2)
self.fake_gerrit.addEvent(A.addApproval('Approved', 1))
self.waitUntilSettled()
self.assertEqual(A.data['status'], 'MERGED')
self.fake_gerrit.addEvent(A.getChangeMergedEvent())
self.waitUntilSettled()
in_repo_conf = textwrap.dedent(
"""
- job:
parent: base
name: project2-test
nodeset: project2-nodeset
- project:
check:
jobs:
- project2-test
gate:
jobs:
- noop
""")
file_dict = {'zuul.yaml': in_repo_conf}
B = self.fake_gerrit.addFakeChange('org/project2', 'stable', 'B',
files=file_dict)
self.fake_gerrit.addEvent(B.getPatchsetCreatedEvent(1))
self.waitUntilSettled()
self.assertEqual(B.reported, 1, "B should report success")
self.assertHistory([
dict(name='project2-test', result='SUCCESS', changes='2,1',
node='ubuntu-xenial'),
])
def test_nodeset_branch_duplicate(self):
# Test that we can create a duplicate secret on a different
# branch of the same project -- i.e., that when we branch
# master to stable on a project with a secret, nothing
# changes.
self.create_branch('org/project1', 'stable')
self.fake_gerrit.addEvent(
self.fake_gerrit.getFakeBranchCreatedEvent(
'org/project1', 'stable'))
self.waitUntilSettled()
A = self.fake_gerrit.addFakeChange('org/project1', 'stable', 'A')
self.fake_gerrit.addEvent(A.getPatchsetCreatedEvent(1))
self.waitUntilSettled()
self.assertEqual(A.reported, 1,
"A should report success")
self.assertHistory([
dict(name='project1-test', result='SUCCESS', changes='1,1',
node='ubuntu-xenial'),
])
def test_nodeset_branch_error_same_branch(self):
# Test that we are unable to define a nodeset twice on the same
# project-branch.
in_repo_conf = textwrap.dedent(
"""
- nodeset:
name: project1-nodeset
nodes: []
- nodeset:
name: project1-nodeset
nodes: []
""")
file_dict = {'zuul.yaml': in_repo_conf}
A = self.fake_gerrit.addFakeChange('org/project1', 'master', 'A',
files=file_dict)
self.fake_gerrit.addEvent(A.getPatchsetCreatedEvent(1))
self.waitUntilSettled()
self.assertIn('already defined', A.messages[0])
def test_nodeset_branch_error_same_project(self):
# Test that we are unable to create a nodeset which differs
# from another with the same name -- i.e., that if we have a
# duplicate nodeset on multiple branches of the same project,
# they must be identical.
self.create_branch('org/project1', 'stable')
self.fake_gerrit.addEvent(
self.fake_gerrit.getFakeBranchCreatedEvent(
'org/project1', 'stable'))
self.waitUntilSettled()
in_repo_conf = textwrap.dedent(
"""
- nodeset:
name: project1-nodeset
nodes: []
""")
file_dict = {'zuul.yaml': in_repo_conf}
A = self.fake_gerrit.addFakeChange('org/project1', 'stable', 'A',
files=file_dict)
self.fake_gerrit.addEvent(A.getPatchsetCreatedEvent(1))
self.waitUntilSettled()
self.assertIn('does not match existing definition in branch master',
A.messages[0])
def test_nodeset_branch_error_other_project(self):
# Test that we are unable to create a nodeset with the same
# name as another. We're never allowed to have a nodeset with
# the same name outside of a project.
in_repo_conf = textwrap.dedent(
"""
- nodeset:
name: project1-nodeset
nodes: []
""")
file_dict = {'zuul.yaml': in_repo_conf}
A = self.fake_gerrit.addFakeChange('org/project2', 'master', 'A',
files=file_dict)
self.fake_gerrit.addEvent(A.getPatchsetCreatedEvent(1))
self.waitUntilSettled()
self.assertIn('already defined in project org/project1',
A.messages[0])
class TestJobOutput(AnsibleZuulTestCase):
tenant_config_file = 'config/job-output/main.yaml'

View File

@ -407,7 +407,7 @@ class NodeSetParser(object):
@staticmethod
def fromYaml(conf, anonymous=False):
NodeSetParser.getSchema(anonymous)(conf)
ns = model.NodeSet(conf.get('name'))
ns = model.NodeSet(conf.get('name'), conf.get('_source_context'))
node_names = set()
group_names = set()
for conf_node in as_list(conf['nodes']):

View File

@ -476,8 +476,9 @@ class NodeSet(object):
or they may appears anonymously in in-line job definitions.
"""
def __init__(self, name=None):
def __init__(self, name=None, source_context=None):
self.name = name or ''
self.source_context = source_context
self.nodes = OrderedDict()
self.groups = OrderedDict()
@ -2586,8 +2587,23 @@ class Layout(object):
return True
def addNodeSet(self, nodeset):
if nodeset.name in self.nodesets:
raise Exception("NodeSet %s already defined" % (nodeset.name,))
# It's ok to have a duplicate nodeset definition, but only if
# they are in different branches of the same repo, and have
# the same values.
other = self.nodesets.get(nodeset.name)
if other is not None:
if not nodeset.source_context.isSameProject(other.source_context):
raise Exception("Nodeset %s already defined in project %s" %
(nodeset.name, other.source_context.project))
if nodeset.source_context.branch == other.source_context.branch:
raise Exception("Nodeset %s already defined" % (nodeset.name,))
if nodeset != other:
raise Exception("Nodeset %s does not match existing definition"
" in branch %s" %
(nodeset.name, other.source_context.branch))
# Identical data in a different branch of the same project;
# ignore the duplicate definition
return
self.nodesets[nodeset.name] = nodeset
def addSecret(self, secret):
@ -2595,7 +2611,7 @@ class Layout(object):
# they are in different branches of the same repo, and have
# the same values.
other = self.secrets.get(secret.name)
if other:
if other is not None:
if not secret.source_context.isSameProject(other.source_context):
raise Exception("Secret %s already defined in project %s" %
(secret.name, other.source_context.project))