zuul/tests/unit/test_configloader.py

1016 lines
43 KiB
Python

# Copyright 2017 Red Hat, Inc.
#
# 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 fixtures
import logging
import textwrap
import testtools
import voluptuous as vs
from collections import defaultdict
from configparser import ConfigParser
from zuul import model
from zuul.lib.ansible import AnsibleManager
from zuul.configloader import (
AuthorizationRuleParser, ConfigLoader, safe_load_yaml
)
from zuul.model import Abide, MergeRequest, SourceContext
from zuul.zk.locks import tenant_read_lock
from tests.base import ZuulTestCase
class TestConfigLoader(ZuulTestCase):
tenant_config_file = 'config/single-tenant/main.yaml'
def test_update_system_config(self):
"""Test if the system config can be updated without a scheduler."""
sched = self.scheds.first.sched
# Get the current system config before instantiating a ConfigLoader.
unparsed_abide, zuul_globals = sched.system_config_cache.get()
ansible_manager = AnsibleManager(
default_version=zuul_globals.default_ansible_version)
loader = ConfigLoader(
sched.connections, self.zk_client, zuul_globals, sched.statsd,
keystorage=sched.keystore)
abide = Abide()
loader.loadTPCs(abide, unparsed_abide)
loader.loadAdminRules(abide, unparsed_abide)
for tenant_name in unparsed_abide.tenants:
tlock = tenant_read_lock(self.zk_client, tenant_name)
# Consider all caches valid (min. ltime -1)
min_ltimes = defaultdict(lambda: defaultdict(lambda: -1))
with tlock:
tenant = loader.loadTenant(
abide, tenant_name, ansible_manager, unparsed_abide,
min_ltimes=min_ltimes)
self.assertEqual(tenant.name, tenant_name)
class TenantParserTestCase(ZuulTestCase):
create_project_keys = True
CONFIG_SET = set(['pipeline', 'job', 'semaphore', 'project',
'project-template', 'nodeset', 'secret', 'queue'])
UNTRUSTED_SET = CONFIG_SET - set(['pipeline'])
def setupAllProjectKeys(self, config: ConfigParser):
for project in ['common-config', 'org/project1', 'org/project2']:
self.setupProjectKeys('gerrit', project)
class TestTenantSimple(TenantParserTestCase):
tenant_config_file = 'config/tenant-parser/simple.yaml'
def test_tenant_simple(self):
tenant = self.scheds.first.sched.abide.tenants.get('tenant-one')
self.assertEqual(['common-config'],
[x.name for x in tenant.config_projects])
self.assertEqual(['org/project1', 'org/project2'],
[x.name for x in tenant.untrusted_projects])
project = tenant.config_projects[0]
tpc = tenant.project_configs[project.canonical_name]
self.assertEqual(self.CONFIG_SET, tpc.load_classes)
project = tenant.untrusted_projects[0]
tpc = tenant.project_configs[project.canonical_name]
self.assertEqual(self.UNTRUSTED_SET, tpc.load_classes)
project = tenant.untrusted_projects[1]
tpc = tenant.project_configs[project.canonical_name]
self.assertEqual(self.UNTRUSTED_SET, tpc.load_classes)
self.assertTrue('common-config-job' in tenant.layout.jobs)
self.assertTrue('project1-job' in tenant.layout.jobs)
self.assertTrue('project2-job' in tenant.layout.jobs)
project1_config = tenant.layout.project_configs.get(
'review.example.com/org/project1')
self.assertTrue('common-config-job' in
project1_config[0].pipelines['check'].job_list.jobs)
self.assertTrue('project1-job' in
project1_config[1].pipelines['check'].job_list.jobs)
project2_config = tenant.layout.project_configs.get(
'review.example.com/org/project2')
self.assertTrue('common-config-job' in
project2_config[0].pipelines['check'].job_list.jobs)
self.assertTrue('project2-job' in
project2_config[1].pipelines['check'].job_list.jobs)
def test_variant_description(self):
tenant = self.scheds.first.sched.abide.tenants.get('tenant-one')
job = tenant.layout.jobs.get("project2-job")
self.assertEqual(job[0].variant_description, "")
self.assertEqual(job[1].variant_description, "stable")
def test_merge_anchor(self):
to_parse = textwrap.dedent(
"""
- job:
name: job1
vars: &docker_vars
registry: 'registry.example.org'
- job:
name: job2
vars:
<<: &buildenv_vars
image_name: foo
<<: *docker_vars
- job:
name: job3
vars:
<<: *buildenv_vars
<<: *docker_vars
""")
tenant = self.scheds.first.sched.abide.tenants.get('tenant-one')
project = tenant.config_projects[0]
source_context = SourceContext(
project.canonical_name, project.name, project.connection_name,
'master', 'zuul.yaml', True)
data = safe_load_yaml(to_parse, source_context)
self.assertEqual(len(data), 3)
job_vars = [i['job']['vars'] for i in data]
# Test that merging worked
self.assertEqual(job_vars, [
{'registry': 'registry.example.org'},
{'registry': 'registry.example.org', 'image_name': 'foo'},
{'registry': 'registry.example.org', 'image_name': 'foo'},
])
def test_deny_localhost_nodeset(self):
in_repo_conf = textwrap.dedent(
"""
- nodeset:
name: localhost
nodes:
- name: localhost
label: ubuntu
""")
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()
# No job should have run due to the change introducing a config error
self.assertHistory([])
self.assertTrue(A.reported)
self.assertTrue("Nodes named 'localhost' are not allowed."
in A.messages[0])
in_repo_conf = textwrap.dedent(
"""
- nodeset:
name: localhost-group
nodes:
- name: ubuntu
label: ubuntu
groups:
- name: localhost
nodes: ubuntu
""")
file_dict = {'zuul.yaml': in_repo_conf}
B = self.fake_gerrit.addFakeChange('org/project1', 'master', 'A',
files=file_dict)
self.fake_gerrit.addEvent(B.getPatchsetCreatedEvent(1))
self.waitUntilSettled()
# No job should have run due to the change introducing a config error
self.assertHistory([])
self.assertTrue(B.reported)
self.assertTrue("Groups named 'localhost' are not allowed."
in B.messages[0])
class TestTenantOverride(TenantParserTestCase):
tenant_config_file = 'config/tenant-parser/override.yaml'
def test_tenant_override(self):
tenant = self.scheds.first.sched.abide.tenants.get('tenant-one')
self.assertEqual(['common-config'],
[x.name for x in tenant.config_projects])
self.assertEqual(['org/project1', 'org/project2', 'org/project4'],
[x.name for x in tenant.untrusted_projects])
project = tenant.config_projects[0]
tpc = tenant.project_configs[project.canonical_name]
self.assertEqual(self.CONFIG_SET, tpc.load_classes)
project = tenant.untrusted_projects[0]
tpc = tenant.project_configs[project.canonical_name]
self.assertEqual(self.UNTRUSTED_SET - set(['project']),
tpc.load_classes)
project = tenant.untrusted_projects[1]
tpc = tenant.project_configs[project.canonical_name]
self.assertEqual(set(['job']), tpc.load_classes)
self.assertTrue('common-config-job' in tenant.layout.jobs)
self.assertTrue('project1-job' in tenant.layout.jobs)
self.assertTrue('project2-job' in tenant.layout.jobs)
project1_config = tenant.layout.project_configs.get(
'review.example.com/org/project1')
self.assertTrue('common-config-job' in
project1_config[0].pipelines['check'].job_list.jobs)
self.assertFalse('project1-job' in
project1_config[0].pipelines['check'].job_list.jobs)
project2_config = tenant.layout.project_configs.get(
'review.example.com/org/project2')
self.assertTrue('common-config-job' in
project2_config[0].pipelines['check'].job_list.jobs)
self.assertFalse('project2-job' in
project2_config[0].pipelines['check'].job_list.jobs)
class TestTenantGroups(TenantParserTestCase):
tenant_config_file = 'config/tenant-parser/groups.yaml'
def test_tenant_groups(self):
tenant = self.scheds.first.sched.abide.tenants.get('tenant-one')
self.assertEqual(['common-config'],
[x.name for x in tenant.config_projects])
self.assertEqual(['org/project1', 'org/project2'],
[x.name for x in tenant.untrusted_projects])
project = tenant.config_projects[0]
tpc = tenant.project_configs[project.canonical_name]
self.assertEqual(self.CONFIG_SET, tpc.load_classes)
project = tenant.untrusted_projects[0]
tpc = tenant.project_configs[project.canonical_name]
self.assertEqual(self.UNTRUSTED_SET - set(['project']),
tpc.load_classes)
project = tenant.untrusted_projects[1]
tpc = tenant.project_configs[project.canonical_name]
self.assertEqual(self.UNTRUSTED_SET - set(['project']),
tpc.load_classes)
self.assertTrue('common-config-job' in tenant.layout.jobs)
self.assertTrue('project1-job' in tenant.layout.jobs)
self.assertTrue('project2-job' in tenant.layout.jobs)
project1_config = tenant.layout.project_configs.get(
'review.example.com/org/project1')
self.assertTrue('common-config-job' in
project1_config[0].pipelines['check'].job_list.jobs)
self.assertFalse('project1-job' in
project1_config[0].pipelines['check'].job_list.jobs)
project2_config = tenant.layout.project_configs.get(
'review.example.com/org/project2')
self.assertTrue('common-config-job' in
project2_config[0].pipelines['check'].job_list.jobs)
self.assertFalse('project2-job' in
project2_config[0].pipelines['check'].job_list.jobs)
class TestTenantGroups2(TenantParserTestCase):
tenant_config_file = 'config/tenant-parser/groups2.yaml'
def test_tenant_groups2(self):
tenant = self.scheds.first.sched.abide.tenants.get('tenant-one')
self.assertEqual(['common-config'],
[x.name for x in tenant.config_projects])
self.assertEqual(['org/project1', 'org/project2', 'org/project3'],
[x.name for x in tenant.untrusted_projects])
project = tenant.config_projects[0]
tpc = tenant.project_configs[project.canonical_name]
self.assertEqual(self.CONFIG_SET, tpc.load_classes)
project = tenant.untrusted_projects[0]
tpc = tenant.project_configs[project.canonical_name]
self.assertEqual(self.UNTRUSTED_SET - set(['project']),
tpc.load_classes)
project = tenant.untrusted_projects[1]
tpc = tenant.project_configs[project.canonical_name]
self.assertEqual(self.UNTRUSTED_SET - set(['project', 'job']),
tpc.load_classes)
self.assertTrue('common-config-job' in tenant.layout.jobs)
self.assertTrue('project1-job' in tenant.layout.jobs)
self.assertFalse('project2-job' in tenant.layout.jobs)
project1_config = tenant.layout.project_configs.get(
'review.example.com/org/project1')
self.assertTrue('common-config-job' in
project1_config[0].pipelines['check'].job_list.jobs)
self.assertFalse('project1-job' in
project1_config[0].pipelines['check'].job_list.jobs)
project2_config = tenant.layout.project_configs.get(
'review.example.com/org/project2')
self.assertTrue('common-config-job' in
project2_config[0].pipelines['check'].job_list.jobs)
self.assertFalse('project2-job' in
project2_config[0].pipelines['check'].job_list.jobs)
class TestTenantGroups3(TenantParserTestCase):
tenant_config_file = 'config/tenant-parser/groups3.yaml'
def test_tenant_groups3(self):
tenant = self.scheds.first.sched.abide.tenants.get('tenant-one')
self.assertEqual(False, tenant.exclude_unprotected_branches)
self.assertEqual(['common-config'],
[x.name for x in tenant.config_projects])
self.assertEqual(['org/project1', 'org/project2'],
[x.name for x in tenant.untrusted_projects])
project = tenant.config_projects[0]
tpc = tenant.project_configs[project.canonical_name]
self.assertEqual(self.CONFIG_SET, tpc.load_classes)
project = tenant.untrusted_projects[0]
tpc = tenant.project_configs[project.canonical_name]
self.assertEqual(set(['job']), tpc.load_classes)
project = tenant.untrusted_projects[1]
tpc = tenant.project_configs[project.canonical_name]
self.assertEqual(set(['project', 'job']), tpc.load_classes)
self.assertTrue('common-config-job' in tenant.layout.jobs)
self.assertTrue('project1-job' in tenant.layout.jobs)
self.assertTrue('project2-job' in tenant.layout.jobs)
project1_config = tenant.layout.project_configs.get(
'review.example.com/org/project1')
self.assertTrue('common-config-job' in
project1_config[0].pipelines['check'].job_list.jobs)
self.assertFalse('project1-job' in
project1_config[0].pipelines['check'].job_list.jobs)
project2_config = tenant.layout.project_configs.get(
'review.example.com/org/project2')
self.assertTrue('common-config-job' in
project2_config[0].pipelines['check'].job_list.jobs)
self.assertTrue('project2-job' in
project2_config[1].pipelines['check'].job_list.jobs)
class TestTenantGroups4(TenantParserTestCase):
tenant_config_file = 'config/tenant-parser/groups4.yaml'
def test_tenant_groups(self):
tenant = self.scheds.first.sched.abide.tenants.get('tenant-one')
self.assertEqual(['common-config'],
[x.name for x in tenant.config_projects])
self.assertEqual(['org/project1', 'org/project2'],
[x.name for x in tenant.untrusted_projects])
project = tenant.config_projects[0]
tpc = tenant.project_configs[project.canonical_name]
self.assertEqual(self.CONFIG_SET, tpc.load_classes)
project = tenant.untrusted_projects[0]
tpc = tenant.project_configs[project.canonical_name]
self.assertEqual(set([]),
tpc.load_classes)
project = tenant.untrusted_projects[1]
tpc = tenant.project_configs[project.canonical_name]
self.assertEqual(set([]),
tpc.load_classes)
# Check that only one merger:cat job was requested
# org/project1 and org/project2 have an empty load_classes
cat_jobs = [job for job in self.merge_job_history.values()
if job.job_type == MergeRequest.CAT]
self.assertEqual(1, len(cat_jobs))
old_layout = tenant.layout
# Check that creating a change in project1 doesn't cause a
# reconfiguration (due to a mistaken belief that we need to
# load config from it since there is none in memory).
A = self.fake_gerrit.addFakeChange('org/project1', 'master', 'A')
self.fake_gerrit.addEvent(A.getPatchsetCreatedEvent(1))
self.waitUntilSettled()
tenant = self.scheds.first.sched.abide.tenants.get('tenant-one')
new_layout = tenant.layout
self.assertEqual(old_layout, new_layout)
class TestTenantGroups5(TenantParserTestCase):
tenant_config_file = 'config/tenant-parser/groups5.yaml'
def test_tenant_single_projet_exclude(self):
tenant = self.scheds.first.sched.abide.tenants.get('tenant-one')
self.assertEqual(['common-config'],
[x.name for x in tenant.config_projects])
self.assertEqual(['org/project1'],
[x.name for x in tenant.untrusted_projects])
project = tenant.config_projects[0]
tpc = tenant.project_configs[project.canonical_name]
self.assertEqual(self.CONFIG_SET, tpc.load_classes)
project = tenant.untrusted_projects[0]
tpc = tenant.project_configs[project.canonical_name]
self.assertEqual(set([]),
tpc.load_classes)
# Check that only one merger:cat job was requested
# org/project1 and org/project2 have an empty load_classes
cat_jobs = [job for job in self.merge_job_history.values()
if job.job_type == MergeRequest.CAT]
self.assertEqual(1, len(cat_jobs))
class TestTenantFromScript(TestTenantSimple):
tenant_config_file = None
tenant_config_script_file = 'config/tenant-parser/tenant_config_script.py'
def test_tenant_simple(self):
TestTenantSimple.test_tenant_simple(self)
class TestTenantUnprotectedBranches(TenantParserTestCase):
tenant_config_file = 'config/tenant-parser/unprotected-branches.yaml'
def test_tenant_unprotected_branches(self):
tenant = self.scheds.first.sched.abide.tenants.get('tenant-one')
self.assertEqual(True, tenant.exclude_unprotected_branches)
self.assertEqual(['common-config'],
[x.name for x in tenant.config_projects])
self.assertEqual(['org/project1', 'org/project2'],
[x.name for x in tenant.untrusted_projects])
tpc = tenant.project_configs
project_name = tenant.config_projects[0].canonical_name
self.assertEqual(False, tpc[project_name].exclude_unprotected_branches)
project_name = tenant.untrusted_projects[0].canonical_name
self.assertIsNone(tpc[project_name].exclude_unprotected_branches)
project_name = tenant.untrusted_projects[1].canonical_name
self.assertIsNone(tpc[project_name].exclude_unprotected_branches)
class TestTenantExcludeAll(TenantParserTestCase):
tenant_config_file = 'config/tenant-parser/exclude-all.yaml'
def test_tenant_exclude_all(self):
"""
Tests that excluding all configuration of project1 in tenant-one
doesn't remove the configuration of project1 in tenant-two.
"""
# The config in org/project5 depends on config in org/project1 so
# validate that there are no config errors in that tenant.
tenant_two = self.scheds.first.sched.abide.tenants.get('tenant-two')
self.assertEquals(
len(tenant_two.layout.loading_errors), 0,
"No error should have been accumulated")
class TestTenantConfigBranches(ZuulTestCase):
tenant_config_file = 'config/tenant-parser/simple.yaml'
def _validate_job(self, job, branch):
tenant_one = self.scheds.first.sched.abide.tenants.get('tenant-one')
jobs = tenant_one.layout.getJobs(job)
self.assertEquals(len(jobs), 1)
self.assertIn(jobs[0].source_context.branch, branch)
def test_tenant_config_load_branch(self):
"""
Tests that when specifying branches for a project only those branches
are parsed.
"""
# Job must be defined in master
common_job = 'common-config-job'
self._validate_job(common_job, 'master')
self.log.debug('Creating branches')
self.create_branch('common-config', 'stable')
self.create_branch('common-config', 'feat_x')
self.fake_gerrit.addEvent(
self.fake_gerrit.getFakeBranchCreatedEvent(
'common-config', 'stable'))
self.fake_gerrit.addEvent(
self.fake_gerrit.getFakeBranchCreatedEvent(
'common-config', 'feat_x'))
self.waitUntilSettled()
# Job must be defined in master
self._validate_job(common_job, 'master')
# Reconfigure with load-branch stable for common-config
self.newTenantConfig('config/tenant-parser/branch.yaml')
self.scheds.execute(lambda app: app.sched.reconfigure(app.config))
# Now job must be defined on stable branch
self._validate_job(common_job, 'stable')
# Now try to break the config in common-config on stable
in_repo_conf = textwrap.dedent(
"""
- job:
name: base
parent: non-existing
""")
file_dict = {'zuul.yaml': in_repo_conf}
A = self.fake_gerrit.addFakeChange('common-config', 'stable', 'A',
files=file_dict)
self.fake_gerrit.addEvent(A.getPatchsetCreatedEvent(1))
self.waitUntilSettled()
# No job should have run due to the change introducing a config error
self.assertHistory([])
self.assertTrue(A.reported)
self.assertTrue('Job non-existing not defined' in A.messages[0])
class TestSplitConfig(ZuulTestCase):
tenant_config_file = 'config/split-config/main.yaml'
def test_split_config(self):
tenant = self.scheds.first.sched.abide.tenants.get('tenant-one')
self.assertIn('project-test1', tenant.layout.jobs)
self.assertIn('project-test2', tenant.layout.jobs)
test1 = tenant.layout.getJob('project-test1')
self.assertEqual(test1.source_context.project_name, 'common-config')
self.assertEqual(test1.source_context.branch, 'master')
self.assertEqual(test1.source_context.path, 'zuul.d/jobs.yaml')
self.assertEqual(test1.source_context.trusted, True)
test2 = tenant.layout.getJob('project-test2')
self.assertEqual(test2.source_context.project_name, 'common-config')
self.assertEqual(test2.source_context.branch, 'master')
self.assertEqual(test2.source_context.path, 'zuul.d/more-jobs.yaml')
self.assertEqual(test2.source_context.trusted, True)
self.assertNotEqual(test1.source_context, test2.source_context)
self.assertTrue(test1.source_context.isSameProject(
test2.source_context))
project_config = tenant.layout.project_configs.get(
'review.example.com/org/project')
self.assertIn('project-test1',
project_config[0].pipelines['check'].job_list.jobs)
project1_config = tenant.layout.project_configs.get(
'review.example.com/org/project1')
self.assertIn('project1-project2-integration',
project1_config[0].pipelines['check'].job_list.jobs)
# This check ensures the .zuul.ignore flag file is working in
# the config directory.
self.assertEquals(
len(tenant.layout.loading_errors), 0)
def test_dynamic_split_config(self):
in_repo_conf = textwrap.dedent(
"""
- project:
name: org/project1
check:
jobs:
- project-test1
""")
file_dict = {'.zuul.d/gate.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()
# project1-project2-integration test removed, only want project-test1
self.assertHistory([
dict(name='project-test1', result='SUCCESS', changes='1,1')])
def test_config_path_conflict(self):
def add_file(project, path):
new_file = textwrap.dedent(
"""
- job:
name: test-job
"""
)
file_dict = {path: new_file}
A = self.fake_gerrit.addFakeChange(project, 'master', 'A',
files=file_dict)
self.fake_gerrit.addEvent(A.getPatchsetCreatedEvent(1))
self.waitUntilSettled()
return A
log_fixture = self.useFixture(
fixtures.FakeLogger(level=logging.WARNING))
log_fixture._output.truncate(0)
A = add_file("common-config", "zuul.yaml")
self.assertIn("Configuration in common-config/zuul.d/jobs.yaml@master "
"ignored because project-branch is already configured",
log_fixture.output)
self.assertIn("Configuration in common-config/zuul.d/jobs.yaml@master "
"ignored because project-branch is already configured",
A.messages[0])
log_fixture._output.truncate(0)
add_file("org/project1", ".zuul.yaml")
self.assertIn("Configuration in org/project1/.zuul.d/gate.yaml@master "
"ignored because project-branch is already configured",
log_fixture.output)
class TestConfigConflict(ZuulTestCase):
tenant_config_file = 'config/conflict-config/main.yaml'
def test_conflict_config(self):
tenant = self.scheds.first.sched.abide.tenants.get('tenant-one')
jobs = sorted(tenant.layout.jobs.keys())
self.assertEqual(
['base', 'noop', 'trusted-zuul.yaml-job',
'untrusted-zuul.yaml-job'],
jobs)
class TestUnparsedConfigCache(ZuulTestCase):
tenant_config_file = 'config/single-tenant/main.yaml'
def test_config_caching(self):
sched = self.scheds.first.sched
cache = sched.unparsed_config_cache
tenant = sched.abide.tenants["tenant-one"]
common_cache = cache.getFilesCache("review.example.com/common-config",
"master")
upb_common_cache = sched.abide.getUnparsedBranchCache(
"review.example.com/common-config", "master")
tpc = tenant.project_configs["review.example.com/common-config"]
self.assertTrue(common_cache.isValidFor(tpc, min_ltime=-1))
self.assertEqual(len(common_cache), 1)
self.assertIn("zuul.yaml", common_cache)
self.assertTrue(len(common_cache["zuul.yaml"]) > 0)
self.assertEqual(upb_common_cache.ltime, common_cache.ltime)
project_cache = cache.getFilesCache("review.example.com/org/project",
"master")
upb_project_cache = sched.abide.getUnparsedBranchCache(
"review.example.com/org/project", "master")
# Cache of org/project should be valid but empty (no in-repo config)
tpc = tenant.project_configs["review.example.com/org/project"]
self.assertTrue(project_cache.isValidFor(tpc, min_ltime=-1))
self.assertEqual(len(project_cache), 0)
self.assertEqual(upb_project_cache.ltime, project_cache.ltime)
def test_cache_use(self):
sched = self.scheds.first.sched
# Stop cleanup jobs so it's not removing projects from
# the cache during the test.
sched.apsched.shutdown()
tenant = sched.abide.tenants['tenant-one']
_, project = tenant.getProject('org/project2')
cache = self.scheds.first.sched.unparsed_config_cache
files_cache = cache.getFilesCache(
"review.example.com/org/project2", "master")
zk_initial_ltime = files_cache.ltime
upb_cache = sched.abide.getUnparsedBranchCache(
"review.example.com/org/project2", "master")
self.assertEqual(zk_initial_ltime, upb_cache.ltime)
# Get the current ltime from Zookeeper and run a full reconfiguration,
# so that we know all items in the cache have a larger ltime.
ltime = self.zk_client.getCurrentLtime()
self.scheds.first.fullReconfigure()
self.assertGreater(files_cache.ltime, zk_initial_ltime)
upb_cache = sched.abide.getUnparsedBranchCache(
"review.example.com/org/project2", "master")
self.assertEqual(files_cache.ltime, upb_cache.ltime)
# Clear the unparsed branch cache so all projects (except for
# org/project2) are retrieved from the cache in Zookeeper.
sched.abide.unparsed_project_branch_cache.clear()
del self.merge_job_history
# Create a tenant reconfiguration event with a known ltime that is
# smaller than the ltime of the items in the cache.
event = model.TenantReconfigureEvent(
tenant.name, project.canonical_name, branch_name=None)
event.zuul_event_ltime = ltime
sched.management_events[tenant.name].put(event, needs_result=False)
self.waitUntilSettled()
# As the cache should be valid (cache ltime of org/project2 newer than
# event ltime) we don't expect any cat jobs.
cat_jobs = [job for job in self.merge_job_history.values()
if job.job_type == MergeRequest.CAT]
self.assertEqual(len(cat_jobs), 0)
# Set canary value so we can detect if the configloader used
# the cache in Zookeeper (it shouldn't).
common_cache = cache.getFilesCache("review.example.com/common-config",
"master")
common_cache.setValidFor({"CANARY"}, set(), common_cache.ltime)
del self.merge_job_history
# Create a tenant reconfiguration event with a known ltime that is
# smaller than the ltime of the items in the cache.
event = model.TenantReconfigureEvent(
tenant.name, project.canonical_name, branch_name=None)
event.zuul_event_ltime = ltime
sched.management_events[tenant.name].put(event, needs_result=False)
self.waitUntilSettled()
upb_cache = sched.abide.getUnparsedBranchCache(
"review.example.com/common-config", "master")
self.assertEqual(common_cache.ltime, upb_cache.ltime)
self.assertNotIn("CANARY", upb_cache.extra_files_searched)
# As the cache should be valid (cache ltime of org/project2 newer than
# event ltime) we don't expect any cat jobs.
cat_jobs = [job for job in self.merge_job_history.values()
if job.job_type == MergeRequest.CAT]
self.assertEqual(len(cat_jobs), 0)
sched.apsched.start()
class TestAuthorizationRuleParser(ZuulTestCase):
tenant_config_file = 'config/tenant-parser/authorizations.yaml'
def test_rules_are_loaded(self):
rules = self.scheds.first.sched.abide.admin_rules
self.assertTrue('auth-rule-one' in rules,
self.scheds.first.sched.abide)
self.assertTrue('auth-rule-two' in rules,
self.scheds.first.sched.abide)
claims_1 = {'sub': 'venkman'}
claims_2 = {'sub': 'gozer',
'iss': 'another_dimension'}
self.assertTrue(rules['auth-rule-one'](claims_1))
self.assertTrue(not rules['auth-rule-one'](claims_2))
self.assertTrue(not rules['auth-rule-two'](claims_1))
self.assertTrue(rules['auth-rule-two'](claims_2))
def test_parse_simplest_rule_from_yaml(self):
rule_d = {'name': 'my-rule',
'conditions': {'sub': 'user1'}
}
rule = AuthorizationRuleParser().fromYaml(rule_d)
self.assertEqual('my-rule', rule.name)
claims = {'iss': 'my-idp',
'sub': 'user1',
'groups': ['admin', 'ghostbusters']}
self.assertTrue(rule(claims))
claims = {'iss': 'my-2nd-idp',
'sub': 'user2',
'groups': ['admin', 'ghostbusters']}
self.assertFalse(rule(claims))
def test_parse_AND_rule_from_yaml(self):
rule_d = {'name': 'my-rule',
'conditions': {'sub': 'user1',
'iss': 'my-idp'}
}
rule = AuthorizationRuleParser().fromYaml(rule_d)
self.assertEqual('my-rule', rule.name)
claims = {'iss': 'my-idp',
'sub': 'user1',
'groups': ['admin', 'ghostbusters']}
self.assertTrue(rule(claims))
claims = {'iss': 'my-2nd-idp',
'sub': 'user1',
'groups': ['admin', 'ghostbusters']}
self.assertFalse(rule(claims))
def test_parse_OR_rule_from_yaml(self):
rule_d = {'name': 'my-rule',
'conditions': [{'sub': 'user1',
'iss': 'my-idp'},
{'sub': 'user2',
'iss': 'my-2nd-idp'}
]
}
rule = AuthorizationRuleParser().fromYaml(rule_d)
self.assertEqual('my-rule', rule.name)
claims = {'iss': 'my-idp',
'sub': 'user1',
'groups': ['admin', 'ghostbusters']}
self.assertTrue(rule(claims))
claims = {'iss': 'my-2nd-idp',
'sub': 'user1',
'groups': ['admin', 'ghostbusters']}
self.assertFalse(rule(claims))
claims = {'iss': 'my-2nd-idp',
'sub': 'user2',
'groups': ['admin', 'ghostbusters']}
self.assertTrue(rule(claims))
def test_parse_rule_with_list_claim_from_yaml(self):
rule_d = {'name': 'my-rule',
'conditions': [{'groups': 'ghostbusters',
'iss': 'my-idp'},
{'sub': 'user2',
'iss': 'my-2nd-idp'}
],
}
rule = AuthorizationRuleParser().fromYaml(rule_d)
self.assertEqual('my-rule', rule.name)
claims = {'iss': 'my-idp',
'sub': 'user1',
'groups': ['admin', 'ghostbusters']}
self.assertTrue(rule(claims))
claims = {'iss': 'my-idp',
'sub': 'user1',
'groups': ['admin', 'ghostbeaters']}
self.assertFalse(rule(claims))
claims = {'iss': 'my-2nd-idp',
'sub': 'user2',
'groups': ['admin', 'ghostbusters']}
self.assertTrue(rule(claims))
def test_check_complex_rule_from_yaml_jsonpath(self):
rule_d = {'name': 'my-rule',
'conditions': [{'hello.this.is': 'a complex value'},
],
}
rule = AuthorizationRuleParser().fromYaml(rule_d)
self.assertEqual('my-rule', rule.name)
claims = {'iss': 'my-idp',
'hello': {
'this': {
'is': 'a complex value'
},
'and': {
'this one': 'too'
}
}
}
self.assertTrue(rule(claims))
def test_check_complex_rule_from_yaml_nested_dict(self):
rule_d = {'name': 'my-rule',
'conditions': [{'hello': {'this': {'is': 'a complex value'
}
}
},
],
}
rule = AuthorizationRuleParser().fromYaml(rule_d)
self.assertEqual('my-rule', rule.name)
claims = {'iss': 'my-idp',
'hello': {
'this': {
'is': 'a complex value'
},
'and': {
'this one': 'too'
}
}
}
self.assertTrue(rule(claims))
class TestAuthorizationRuleParserWithTemplating(ZuulTestCase):
tenant_config_file = 'config/tenant-parser/authorizations-templating.yaml'
def test_rules_are_loaded(self):
rules = self.scheds.first.sched.abide.admin_rules
self.assertTrue('tenant-admin' in rules, self.scheds.first.sched.abide)
self.assertTrue('tenant-admin-complex' in rules,
self.scheds.first.sched.abide)
def test_tenant_substitution(self):
claims_1 = {'group': 'tenant-one-admin'}
claims_2 = {'group': 'tenant-two-admin'}
rules = self.scheds.first.sched.abide.admin_rules
tenant_one = self.scheds.first.sched.abide.tenants.get('tenant-one')
tenant_two = self.scheds.first.sched.abide.tenants.get('tenant-two')
self.assertTrue(rules['tenant-admin'](claims_1, tenant_one))
self.assertTrue(rules['tenant-admin'](claims_2, tenant_two))
self.assertTrue(not rules['tenant-admin'](claims_1, tenant_two))
self.assertTrue(not rules['tenant-admin'](claims_2, tenant_one))
def test_tenant_substitution_in_list(self):
claims_1 = {'group': ['tenant-one-admin', 'some-other-tenant']}
claims_2 = {'group': ['tenant-two-admin', 'some-other-tenant']}
rules = self.scheds.first.sched.abide.admin_rules
tenant_one = self.scheds.first.sched.abide.tenants.get('tenant-one')
tenant_two = self.scheds.first.sched.abide.tenants.get('tenant-two')
self.assertTrue(rules['tenant-admin'](claims_1, tenant_one))
self.assertTrue(rules['tenant-admin'](claims_2, tenant_two))
self.assertTrue(not rules['tenant-admin'](claims_1, tenant_two))
self.assertTrue(not rules['tenant-admin'](claims_2, tenant_one))
def test_tenant_substitution_in_dict(self):
claims_2 = {
'path': {
'to': {
'group': 'tenant-two-admin'
}
}
}
rules = self.scheds.first.sched.abide.admin_rules
tenant_one = self.scheds.first.sched.abide.tenants.get('tenant-one')
tenant_two = self.scheds.first.sched.abide.tenants.get('tenant-two')
self.assertTrue(not rules['tenant-admin-complex'](claims_2,
tenant_one))
self.assertTrue(rules['tenant-admin-complex'](claims_2, tenant_two))
class TestTenantExtra(TenantParserTestCase):
tenant_config_file = 'config/tenant-parser/extra.yaml'
def test_tenant_extra(self):
tenant = self.scheds.first.sched.abide.tenants.get('tenant-one')
self.assertTrue('project2-extra-file' in tenant.layout.jobs)
self.assertTrue('project2-extra-dir' in tenant.layout.jobs)
def test_dynamic_extra(self):
in_repo_conf = textwrap.dedent(
"""
- job:
name: project2-extra-file2
parent: common-config-job
- project:
name: org/project2
check:
jobs:
- project2-extra-file2
""")
file_dict = {'extra.yaml': in_repo_conf, '.zuul.yaml': ''}
A = self.fake_gerrit.addFakeChange('org/project2', 'master', 'A',
files=file_dict)
self.fake_gerrit.addEvent(A.getPatchsetCreatedEvent(1))
self.waitUntilSettled()
self.assertHistory([
dict(name='common-config-job', result='SUCCESS', changes='1,1'),
dict(name='project2-extra-file2', result='SUCCESS', changes='1,1'),
], ordered=False)
def test_extra_reconfigure(self):
in_repo_conf = textwrap.dedent(
"""
- job:
name: project2-extra-file2
parent: common-config-job
- project:
name: org/project2
check:
jobs:
- project2-extra-file2
""")
file_dict = {'extra.yaml': in_repo_conf}
A = self.fake_gerrit.addFakeChange('org/project2', 'master', 'A',
files=file_dict)
A.setMerged()
self.fake_gerrit.addEvent(A.getChangeMergedEvent())
self.waitUntilSettled()
self.fake_gerrit.addEvent(A.getRefUpdatedEvent())
self.waitUntilSettled()
B = self.fake_gerrit.addFakeChange('org/project2', 'master', 'B')
self.fake_gerrit.addEvent(B.getPatchsetCreatedEvent(1))
self.waitUntilSettled()
self.assertHistory([
dict(name='common-config-job', result='SUCCESS', changes='2,1'),
dict(name='project2-job', result='SUCCESS', changes='2,1'),
dict(name='project2-extra-file2', result='SUCCESS', changes='2,1'),
], ordered=False)
class TestTenantExtraConfigsInvalidType(TenantParserTestCase):
tenant_config_file = 'config/tenant-parser/extra_invalid_type.yaml'
# This test raises a config error during the startup of the test
# case which makes the first scheduler fail during its startup.
# The second (or any additional) scheduler won't even run as the
# startup is serialized in tests/base.py.
# Thus it doesn't make sense to execute this test with multiple
# schedulers.
scheduler_count = 1
def setUp(self):
err = "Expected str or list of str for extra-config-paths.*"
with testtools.ExpectedException(vs.MultipleInvalid, err):
super().setUp()
def test_tenant_extra_configs_invalid_type(self):
# The magic is in setUp
pass
class TestTenantExtraConfigsInvalidValue(TenantParserTestCase):
tenant_config_file = 'config/tenant-parser/extra_invalid_value.yaml'
# This test raises a config error during the startup of the test
# case which makes the first scheduler fail during its startup.
# The second (or any additional) scheduler won't even run as the
# startup is serialized in tests/base.py.
# Thus it doesn't make sense to execute this test with multiple
# schedulers.
scheduler_count = 1
def setUp(self):
err = "Default zuul configs are not allowed in extra-config-paths.*"
with testtools.ExpectedException(vs.MultipleInvalid, err):
super().setUp()
def test_tenant_extra_configs_invalid_value(self):
# The magic is in setUp
pass
class TestTenantDuplicate(TenantParserTestCase):
tenant_config_file = 'config/tenant-parser/duplicate.yaml'
# This test raises a config error during the startup of the test
# case which makes the first scheduler fail during its startup.
# The second (or any additional) scheduler won't even run as the
# startup is serialized in tests/base.py.
# Thus it doesn't make sense to execute this test with multiple
# schedulers.
scheduler_count = 1
def setUp(self):
with testtools.ExpectedException(Exception, 'Duplicate configuration'):
super().setUp()
def test_tenant_dupe(self):
# The magic is in setUp
pass