Files
zuul/tests/zuul_client/test_zuulclient.py
James E. Blair a142c44072 Add zuul-client tests for nodes and requests
This adds tests for the recently-added zuul-client commands for
listing and manipulating nodes and nodeset requests.

Depends-On: https://review.opendev.org/c/zuul/zuul-client/+/951725
Change-Id: I6f5641bc379b854c0bd1e2d61db5dd4b92be1d36
2025-06-24 10:02:36 -07:00

932 lines
36 KiB
Python

# Copyright 2020 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 time
import os
import subprocess
import tempfile
import textwrap
from kazoo.exceptions import NoNodeError
import zuul.model
from zuul.lib import yamlutil
from tests.base import (
AnsibleZuulTestCase,
iterate_timeout,
simple_layout,
)
from tests.unit.test_web import BaseTestWeb
from tests.unit.test_launcher import LauncherBaseTestCase
def _split_pretty_table(output):
lines = output.decode().split('\n')
headers = [x.strip() for x in lines[1].split('|') if x != '']
# Trim headers and last line of the table
return [dict(zip(headers,
[x.strip() for x in l.split('|') if x != '']))
for l in lines[3:-2]]
def _split_line_output(output):
lines = output.decode().split('\n')
info = {}
for l in lines:
if l.startswith('==='):
continue
try:
key, value = l.split(':', 1)
info[key] = value.strip()
except ValueError:
continue
return info
class TestSmokeZuulClient(BaseTestWeb):
def test_is_installed(self):
"""Test that the CLI is installed"""
test_version = subprocess.check_output(
['zuul-client', '--version'],
stderr=subprocess.STDOUT)
self.assertTrue(b'Zuul-client version:' in test_version)
class TestZuulClientEncrypt(BaseTestWeb):
"""Test using zuul-client to encrypt secrets"""
tenant_config_file = 'config/secrets/main.yaml'
config_file = 'zuul-admin-web.conf'
secret = {'password': 'zuul-client'}
large_secret = {'key': (('a' * 79 + '\n') * 50)[:-1]}
def setUp(self):
super(TestZuulClientEncrypt, self).setUp()
self.executor_server.hold_jobs_in_build = False
def _getSecrets(self, job, pbtype):
secrets = []
build = self.getJobFromHistory(job)
for pb in getattr(build.jobdir, pbtype):
if pb.secrets_content:
secrets.append(
yamlutil.ansible_unsafe_load(pb.secrets_content))
else:
secrets.append({})
return secrets
def _getClientCommand(self, *args):
return ['zuul-client',
'--zuul-url', self.base_url,
*args]
def test_encrypt_large_secret(self):
"""Test that we can use zuul-client to encrypt a large secret"""
p = subprocess.Popen(
self._getClientCommand(
'encrypt',
'--tenant', 'tenant-one', '--project', 'org/project2',
'--secret-name', 'my_secret', '--field-name', 'key'),
stdout=subprocess.PIPE, stdin=subprocess.PIPE)
p.stdin.write(
str.encode(self.large_secret['key'])
)
output, error = p.communicate()
p.stdin.close()
self._test_encrypt(self.large_secret, output, error)
def test_encrypt(self):
"""Test that we can use zuul-client to generate a project secret"""
p = subprocess.Popen(
self._getClientCommand(
'encrypt',
'--tenant', 'tenant-one', '--project', 'org/project2',
'--secret-name', 'my_secret', '--field-name', 'password'),
stdout=subprocess.PIPE, stdin=subprocess.PIPE)
p.stdin.write(
str.encode(self.secret['password'])
)
output, error = p.communicate()
p.stdin.close()
self._test_encrypt(self.secret, output, error)
def test_encrypt_outfile(self):
"""Test that we can use zuul-client to generate a project secret to a
file"""
outfile = tempfile.NamedTemporaryFile(delete=False)
p = subprocess.Popen(
self._getClientCommand(
'encrypt',
'--tenant', 'tenant-one', '--project', 'org/project2',
'--secret-name', 'my_secret', '--field-name', 'password',
'--outfile', outfile.name),
stdout=subprocess.PIPE, stdin=subprocess.PIPE)
p.stdin.write(
str.encode(self.secret['password'])
)
_, error = p.communicate()
p.stdin.close()
output = outfile.read()
self._test_encrypt(self.secret, output, error)
def test_encrypt_infile(self):
"""Test that we can use zuul-client to generate a project secret from
a file"""
infile = tempfile.NamedTemporaryFile(delete=False)
infile.write(
str.encode(self.secret['password'])
)
infile.close()
p = subprocess.Popen(
self._getClientCommand(
'encrypt',
'--tenant', 'tenant-one', '--project', 'org/project2',
'--secret-name', 'my_secret', '--field-name', 'password',
'--infile', infile.name),
stdout=subprocess.PIPE)
output, error = p.communicate()
os.unlink(infile.name)
self._test_encrypt(self.secret, output, error)
def _test_encrypt(self, _secret, output, error):
self.assertEqual(None, error, error)
self.assertTrue(b'- secret:' in output, output.decode())
new_repo_conf = output.decode()
new_repo_conf += textwrap.dedent(
"""
- job:
parent: base
name: project2-secret
run: playbooks/secret.yaml
secrets:
- my_secret
- project:
check:
jobs:
- project2-secret
gate:
jobs:
- noop
"""
)
file_dict = {'zuul.yaml': new_repo_conf}
A = self.fake_gerrit.addFakeChange('org/project2', 'master',
'Add secret',
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()
# check that the secret is used from there on
B = self.fake_gerrit.addFakeChange('org/project2', 'master',
'test secret',
files={'newfile': 'xxx'})
self.fake_gerrit.addEvent(B.getPatchsetCreatedEvent(1))
self.waitUntilSettled()
self.assertEqual(B.reported, 1, "B should report success")
self.assertHistory([
dict(name='project2-secret', result='SUCCESS', changes='2,1'),
])
secrets = self._getSecrets('project2-secret', 'playbooks')
self.assertEqual(
secrets,
[{'my_secret': _secret}],
secrets)
class TestZuulClientAdmin(BaseTestWeb):
"""Test the admin commands of zuul-client"""
config_file = 'zuul-admin-web.conf'
def test_autohold(self):
"""Test that autohold can be set with the Web client"""
token = self._getToken()
p = subprocess.Popen(
['zuul-client',
'--zuul-url', self.base_url, '--auth-token', token, '-v',
'autohold', '--reason', 'some reason',
'--tenant', 'tenant-one', '--project', 'org/project',
'--job', 'project-test2', '--count', '1'],
stdout=subprocess.PIPE)
output = p.communicate()
self.assertEqual(p.returncode, 0, output)
# Check result
resp = self.get_url(
"api/tenant/tenant-one/autohold",
headers={'Authorization': 'Bearer %s' % token})
self.assertEqual(200, resp.status_code, resp.text)
autohold_requests = resp.json()
self.assertNotEqual([], autohold_requests)
self.assertEqual(1, len(autohold_requests))
request = autohold_requests[0]
self.assertEqual('tenant-one', request['tenant'])
self.assertIn('org/project', request['project'])
self.assertEqual('project-test2', request['job'])
self.assertEqual(".*", request['ref_filter'])
self.assertEqual("some reason", request['reason'])
self.assertEqual(1, request['max_count'])
def test_enqueue(self):
"""Test that the Web client can enqueue a change"""
self.executor_server.hold_jobs_in_build = True
A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
A.addApproval('Code-Review', 2)
A.addApproval('Approved', 1)
token = self._getToken()
p = subprocess.Popen(
['zuul-client',
'--zuul-url', self.base_url, '--auth-token', token, '-v',
'enqueue', '--tenant', 'tenant-one',
'--project', 'org/project',
'--pipeline', 'gate', '--change', '1,1'],
stdout=subprocess.PIPE)
output = p.communicate()
self.assertEqual(p.returncode, 0, output)
self.waitUntilSettled()
# Check the build history for our enqueued build
self.executor_server.hold_jobs_in_build = False
self.executor_server.release()
self.waitUntilSettled()
# project-merge, project-test1, project-test2 in SUCCESS
self.assertEqual(self.countJobResults(self.history, 'SUCCESS'), 3)
def test_enqueue_ref(self):
"""Test that the Web client can enqueue a ref"""
self.executor_server.hold_jobs_in_build = True
p = "review.example.com/org/project"
upstream = self.getUpstreamRepos([p])
A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
A.setMerged()
A_commit = str(upstream[p].commit('master'))
self.log.debug("A commit: %s" % A_commit)
token = self._getToken()
p = subprocess.Popen(
['zuul-client',
'--zuul-url', self.base_url, '--auth-token', token, '-v',
'enqueue-ref', '--tenant', 'tenant-one',
'--project', 'org/project',
'--pipeline', 'post', '--ref', 'master',
'--oldrev', '90f173846e3af9154517b88543ffbd1691f31366',
'--newrev', A_commit],
stdout=subprocess.PIPE)
output = p.communicate()
self.assertEqual(p.returncode, 0, output)
self.waitUntilSettled()
# Check the build history for our enqueued build
self.executor_server.hold_jobs_in_build = False
self.executor_server.release()
self.waitUntilSettled()
self.assertEqual(self.countJobResults(self.history, 'SUCCESS'), 1)
def test_dequeue(self):
"""Test that the Web client can dequeue a change"""
self.executor_server.hold_jobs_in_build = True
start_builds = len(self.builds)
self.create_branch('org/project', 'stable')
self.fake_gerrit.addEvent(
self.fake_gerrit.getFakeBranchCreatedEvent(
'org/project', 'stable'))
self.executor_server.hold_jobs_in_build = True
self.commitConfigUpdate('common-config', 'layouts/timer.yaml')
self.scheds.execute(lambda app: app.sched.reconfigure(app.config))
self.waitUntilSettled()
for _ in iterate_timeout(30, 'Wait for a build on hold'):
if len(self.builds) > start_builds:
break
self.waitUntilSettled()
token = self._getToken()
p = subprocess.Popen(
['zuul-client',
'--zuul-url', self.base_url, '--auth-token', token, '-v',
'dequeue', '--tenant', 'tenant-one', '--project', 'org/project',
'--pipeline', 'periodic', '--ref', 'refs/heads/stable'],
stdout=subprocess.PIPE)
output = p.communicate()
self.assertEqual(p.returncode, 0, output)
self.waitUntilSettled()
self.commitConfigUpdate('common-config',
'layouts/no-timer.yaml')
self.scheds.execute(lambda app: app.sched.reconfigure(app.config))
self.waitUntilSettled()
self.executor_server.hold_jobs_in_build = False
self.executor_server.release()
self.waitUntilSettled()
self.assertEqual(self.countJobResults(self.history, 'ABORTED'), 1)
def test_promote(self):
"Test that the Web client can promote a change"
self.executor_server.hold_jobs_in_build = True
A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
B = self.fake_gerrit.addFakeChange('org/project', 'master', 'B')
C = self.fake_gerrit.addFakeChange('org/project', 'master', 'C')
A.addApproval('Code-Review', 2)
B.addApproval('Code-Review', 2)
C.addApproval('Code-Review', 2)
self.fake_gerrit.addEvent(A.addApproval('Approved', 1))
self.fake_gerrit.addEvent(B.addApproval('Approved', 1))
self.fake_gerrit.addEvent(C.addApproval('Approved', 1))
self.waitUntilSettled()
items = self.getAllItems('tenant-one', 'gate')
enqueue_times = {}
for item in items:
enqueue_times[str(item.changes[0])] = item.enqueue_time
# Promote B and C using the cli
token = self._getToken()
p = subprocess.Popen(
['zuul-client',
'--zuul-url', self.base_url, '--auth-token', token, '-v',
'promote', '--tenant', 'tenant-one',
'--pipeline', 'gate', '--changes', '2,1', '3,1'],
stdout=subprocess.PIPE)
output = p.communicate()
self.assertEqual(p.returncode, 0, output)
self.waitUntilSettled()
# ensure that enqueue times are durable
items = self.getAllItems('tenant-one', 'gate')
for item in items:
self.assertEqual(
enqueue_times[str(item.changes[0])], item.enqueue_time)
self.waitUntilSettled()
self.executor_server.release('.*-merge')
self.waitUntilSettled()
self.executor_server.release('.*-merge')
self.waitUntilSettled()
self.executor_server.release('.*-merge')
self.waitUntilSettled()
self.assertEqual(len(self.builds), 6)
self.assertEqual(self.builds[0].name, 'project-test1')
self.assertEqual(self.builds[1].name, 'project-test2')
self.assertEqual(self.builds[2].name, 'project-test1')
self.assertEqual(self.builds[3].name, 'project-test2')
self.assertEqual(self.builds[4].name, 'project-test1')
self.assertEqual(self.builds[5].name, 'project-test2')
self.assertTrue(self.builds[0].hasChanges(B))
self.assertFalse(self.builds[0].hasChanges(A))
self.assertFalse(self.builds[0].hasChanges(C))
self.assertTrue(self.builds[2].hasChanges(B))
self.assertTrue(self.builds[2].hasChanges(C))
self.assertFalse(self.builds[2].hasChanges(A))
self.assertTrue(self.builds[4].hasChanges(B))
self.assertTrue(self.builds[4].hasChanges(C))
self.assertTrue(self.builds[4].hasChanges(A))
self.executor_server.release()
self.waitUntilSettled()
self.assertEqual(A.data['status'], 'MERGED')
self.assertEqual(A.reported, 2)
self.assertEqual(B.data['status'], 'MERGED')
self.assertEqual(B.reported, 2)
self.assertEqual(C.data['status'], 'MERGED')
self.assertEqual(C.reported, 2)
def test_manage_events(self):
self.executor_server.hold_jobs_in_build = False
A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
self.fake_gerrit.addEvent(A.getPatchsetCreatedEvent(1))
self.waitUntilSettled()
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)
token = self._getToken()
p = subprocess.Popen(
['zuul-client',
'--zuul-url', self.base_url, '--auth-token', token, '-v',
'manage-events', '--tenant', 'tenant-one',
'pause-trigger',
'--reason', 'test trigger paused'],
stdout=subprocess.PIPE)
output = p.communicate()
self.assertEqual(p.returncode, 0, output)
time.sleep(1)
B = self.fake_gerrit.addFakeChange('org/project', 'master', 'B')
self.fake_gerrit.addEvent(B.getPatchsetCreatedEvent(1))
time.sleep(5)
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)
self.assertEqual(0, len(self.builds))
p = subprocess.Popen(
['zuul-client',
'--zuul-url', self.base_url, '--auth-token', token, '-v',
'manage-events', '--tenant', 'tenant-one',
'pause-result',
'--reason', 'test result paused'],
stdout=subprocess.PIPE)
output = p.communicate()
self.assertEqual(p.returncode, 0, output)
time.sleep(5)
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)
self.assertEqual(0, len(self.builds))
p = subprocess.Popen(
['zuul-client',
'--zuul-url', self.base_url, '--auth-token', token, '-v',
'manage-events', '--all-tenants', 'normal'],
stdout=subprocess.PIPE)
output = p.communicate()
self.assertEqual(p.returncode, 0, output)
self.waitUntilSettled()
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='2,1'),
dict(name='project-test1', result='SUCCESS', changes='2,1'),
dict(name='project-test2', result='SUCCESS', changes='2,1'),
], ordered=False)
p = subprocess.Popen(
['zuul-client',
'--zuul-url', self.base_url, '--auth-token', token, '-v',
'manage-events', '--all-tenants', 'discard-trigger'],
stdout=subprocess.PIPE)
output = p.communicate()
self.assertEqual(p.returncode, 0, output)
C = self.fake_gerrit.addFakeChange('org/project', 'master', 'C')
self.fake_gerrit.addEvent(C.getPatchsetCreatedEvent(1))
self.waitUntilSettled()
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='2,1'),
dict(name='project-test1', result='SUCCESS', changes='2,1'),
dict(name='project-test2', result='SUCCESS', changes='2,1'),
], ordered=False)
class TestZuulClientQueryData(BaseTestWeb):
"""Test that zuul-client can fetch builds"""
config_file = 'zuul-sql-driver-mysql.conf'
tenant_config_file = 'config/sql-driver/main.yaml'
def setUp(self):
super(TestZuulClientQueryData, self).setUp()
self.add_base_changes()
def add_base_changes(self):
# change on org/project will run 5 jobs in check
A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
self.fake_gerrit.addEvent(A.getPatchsetCreatedEvent(1))
self.waitUntilSettled()
B = self.fake_gerrit.addFakeChange('org/project1', 'master', 'B')
# fail project-merge on PS1; its 2 dependent jobs will be skipped
self.executor_server.failJob('project-merge', B)
self.fake_gerrit.addEvent(B.getPatchsetCreatedEvent(1))
self.waitUntilSettled()
self.executor_server.hold_jobs_in_build = False
self.executor_server.release()
self.waitUntilSettled()
self.executor_server.hold_jobs_in_build = True
B.addPatchset()
self.fake_gerrit.addEvent(B.getPatchsetCreatedEvent(2))
# change on org/project1 will run 3 jobs in check
self.waitUntilSettled()
# changes on both projects will run 3 jobs in gate each
A.addApproval('Code-Review', 2)
self.fake_gerrit.addEvent(A.addApproval('Approved', 1))
B.addApproval('Code-Review', 2)
self.fake_gerrit.addEvent(B.addApproval('Approved', 1))
self.waitUntilSettled()
self.executor_server.hold_jobs_in_build = False
self.executor_server.release()
self.waitUntilSettled()
class TestZuulClientBuilds(TestZuulClientQueryData,
AnsibleZuulTestCase):
"""Test that zuul-client can fetch builds"""
def test_get_builds(self):
"""Test querying builds"""
p = subprocess.Popen(
['zuul-client',
'--zuul-url', self.base_url,
'builds', '--tenant', 'tenant-one', ],
stdout=subprocess.PIPE)
output, err = p.communicate()
self.assertEqual(p.returncode, 0, output)
results = _split_pretty_table(output)
self.assertEqual(17, len(results), results)
# 5 jobs in check, 3 jobs in gate
p = subprocess.Popen(
['zuul-client',
'--zuul-url', self.base_url,
'builds', '--tenant', 'tenant-one', '--project', 'org/project', ],
stdout=subprocess.PIPE)
output, err = p.communicate()
self.assertEqual(p.returncode, 0, output)
results = _split_pretty_table(output)
self.assertEqual(8, len(results), results)
self.assertTrue(all(x['Project'] == 'org/project' for x in results),
results)
# project-test1 is run 3 times in check, 2 times in gate
p = subprocess.Popen(
['zuul-client',
'--zuul-url', self.base_url,
'builds', '--tenant', 'tenant-one', '--job', 'project-test1', ],
stdout=subprocess.PIPE)
output, err = p.communicate()
self.assertEqual(p.returncode, 0, output)
results = _split_pretty_table(output)
self.assertEqual(5, len(results), results)
self.assertTrue(all(x['Job'] == 'project-test1' for x in results),
results)
# 3 builds in check for 2,1; 3 in check + 3 in gate for 2,2
p = subprocess.Popen(
['zuul-client',
'--zuul-url', self.base_url,
'builds', '--tenant', 'tenant-one', '--change', '2', ],
stdout=subprocess.PIPE)
output, err = p.communicate()
self.assertEqual(p.returncode, 0, output)
results = _split_pretty_table(output)
self.assertEqual(9, len(results), results)
self.assertTrue(all(x['Change or Ref'].startswith('2,')
for x in results),
results)
# 1,3 does not exist
p = subprocess.Popen(
['zuul-client',
'--zuul-url', self.base_url,
'builds', '--tenant', 'tenant-one', '--change', '1',
'--ref', '3', ],
stdout=subprocess.PIPE)
output, err = p.communicate()
self.assertEqual(p.returncode, 0, output)
results = _split_pretty_table(output)
self.assertEqual(0, len(results), results)
for result in ['SUCCESS', 'FAILURE']:
p = subprocess.Popen(
['zuul-client',
'--zuul-url', self.base_url,
'builds', '--tenant', 'tenant-one', '--result', result, ],
stdout=subprocess.PIPE)
job_count = self.countJobResults(self.history, result)
# noop job not included, must be added
if result == 'SUCCESS':
job_count += 1
output, err = p.communicate()
self.assertEqual(p.returncode, 0, output)
results = _split_pretty_table(output)
self.assertEqual(job_count, len(results), results)
if len(results) > 0:
self.assertTrue(all(x['Result'] == result for x in results),
results)
# 6 jobs in gate
p = subprocess.Popen(
['zuul-client',
'--zuul-url', self.base_url,
'builds', '--tenant', 'tenant-one', '--pipeline', 'gate', ],
stdout=subprocess.PIPE)
output, err = p.communicate()
self.assertEqual(p.returncode, 0, output)
results = _split_pretty_table(output)
self.assertEqual(6, len(results), results)
self.assertTrue(all(x['Pipeline'] == 'gate' for x in results),
results)
class TestZuulClientBuildInfo(TestZuulClientQueryData,
AnsibleZuulTestCase):
"""Test that zuul-client can fetch a build's details"""
def test_get_build_info(self):
"""Test querying a specific build"""
test_build = self.history[-1]
p = subprocess.Popen(
['zuul-client',
'--zuul-url', self.base_url,
'build-info', '--tenant', 'tenant-one',
'--uuid', test_build.uuid],
stdout=subprocess.PIPE)
output, err = p.communicate()
self.assertEqual(p.returncode, 0, (output, err))
info = _split_line_output(output)
self.assertEqual(test_build.uuid, info.get('UUID'), test_build)
self.assertEqual(test_build.result, info.get('Result'), test_build)
self.assertEqual(test_build.name, info.get('Job'), test_build)
def test_get_build_artifacts(self):
"""Test querying a specific build's artifacts"""
p = subprocess.Popen(
['zuul-client',
'--zuul-url', self.base_url,
'builds', '--tenant', 'tenant-one', '--job', 'project-test1',
'--limit', '1'],
stdout=subprocess.PIPE)
output, err = p.communicate()
self.assertEqual(p.returncode, 0, output)
results = _split_pretty_table(output)
uuid = results[0]['ID']
p = subprocess.Popen(
['zuul-client',
'--zuul-url', self.base_url,
'build-info', '--tenant', 'tenant-one',
'--uuid', uuid,
'--show-artifacts'],
stdout=subprocess.PIPE)
output, err = p.communicate()
self.assertEqual(p.returncode, 0, (output, err))
artifacts = _split_pretty_table(output)
self.assertTrue(
any(x['name'] == 'tarball' and
x['url'] == 'http://example.com/tarball'
for x in artifacts),
output)
self.assertTrue(
any(x['name'] == 'docs' and
x['url'] == 'http://example.com/docs'
for x in artifacts),
output)
class TestZuulClientJobGraph(BaseTestWeb):
def test_job_graph(self):
"""Test the job-graph command"""
p = subprocess.Popen(
['zuul-client',
'--zuul-url', self.base_url,
'job-graph',
'--tenant', 'tenant-one',
'--pipeline', 'check',
'--project', 'org/project1',
'--branch', 'master',
],
stdout=subprocess.PIPE)
output, err = p.communicate()
self.assertEqual(p.returncode, 0, (output, err))
results = _split_pretty_table(output)
expected = [
{'Job': 'project-merge', 'Dependencies': ''},
{'Job': 'project-test1', 'Dependencies': 'project-merge'},
{'Job': 'project-test2', 'Dependencies': 'project-merge'},
{'Job': 'project1-project2-integration',
'Dependencies': 'project-merge'}
]
self.assertEqual(results, expected)
def test_job_graph_dot(self):
"""Test the job-graph command dot output"""
p = subprocess.Popen(
['zuul-client',
'--format', 'dot',
'--zuul-url', self.base_url,
'job-graph',
'--tenant', 'tenant-one',
'--pipeline', 'check',
'--project', 'org/project1',
'--branch', 'master',
],
stdout=subprocess.PIPE)
output, err = p.communicate()
self.assertEqual(p.returncode, 0, (output, err))
expected = textwrap.dedent('''\
digraph job_graph {
rankdir=LR;
node [shape=box];
"project-merge";
"project-merge" -> "project-test1" [dir=back];
"project-merge" -> "project-test2" [dir=back];
"project-merge" -> "project1-project2-integration" [dir=back];
}
''').encode('utf8')
self.assertEqual(output.strip(), expected.strip())
class TestZuulClientFreezeJob(BaseTestWeb):
def test_freeze_job(self):
"""Test the freeze-job command"""
p = subprocess.Popen(
['zuul-client',
'--zuul-url', self.base_url,
'freeze-job',
'--tenant', 'tenant-one',
'--pipeline', 'check',
'--project', 'org/project1',
'--branch', 'master',
'--job', 'project-test1',
],
stdout=subprocess.PIPE)
output, err = p.communicate()
self.assertEqual(p.returncode, 0, (output, err))
output = output.decode('utf8')
for s in [
'Job: project-test1',
'Branch: master',
'Ansible Version:',
'Workspace Scheme: golang',
('gerrit:common-config:playbooks/project-test1.yaml'
'@master [trusted]'),
]:
self.assertIn(s, output)
class TestZuulClientAdminWithAccessRules(TestZuulClientAdmin):
"""Test the admin commands of zuul-client with access rules"""
config_file = 'zuul-admin-web.conf'
tenant_config_file = 'config/single-tenant/main-access-rules.yaml'
default_token_groups = ['users']
class TestZuulClientEncryptWithAccessRules(TestZuulClientEncrypt):
"""Test using zuul-client to encrypt secrets"""
tenant_config_file = 'config/secrets/main-access-rules.yaml'
def _getClientCommand(self, *args):
token = self._getToken()
return ['zuul-client',
'--zuul-url', self.base_url,
'--auth-token', token, '-v',
*args]
class TestZuulClientNodesets(LauncherBaseTestCase, BaseTestWeb):
"""Test that zuul-client can fetch nodeset info"""
config_file = 'zuul-connections-nodepool.conf'
@simple_layout('layouts/nodepool.yaml', enable_nodepool=True)
def test_node_list(self):
self.executor_server.hold_jobs_in_build = True
A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
self.fake_gerrit.addEvent(A.getPatchsetCreatedEvent(1))
self.waitUntilSettled()
p = subprocess.Popen(
['zuul-client',
'--zuul-url', self.base_url,
'node-list', '--tenant', 'tenant-one',
],
stdout=subprocess.PIPE)
output, err = p.communicate()
self.assertEqual(p.returncode, 0, output)
results = _split_pretty_table(output)
self.assertEqual(1, len(results), results)
r = results[0]
self.assertIsNotNone(r['UUID'])
self.assertEqual("['debian-normal']", r['Labels'])
self.assertEqual('ssh', r['Connection'])
self.assertEqual(
'review.example.com%2Forg%2Fcommon-config/aws-us-east-1-main',
r['Provider'])
self.assertEqual('in-use', r['State'])
self.assertIsNotNone(r['State Time'])
@simple_layout('layouts/nodepool.yaml', enable_nodepool=True)
def test_nodeset_request_list(self):
self.hold_nodeset_requests_in_queue = True
A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
self.fake_gerrit.addEvent(A.getPatchsetCreatedEvent(1))
self.waitUntilSettled()
p = subprocess.Popen(
['zuul-client',
'--zuul-url', self.base_url,
'nodeset-request-list', '--tenant', 'tenant-one',
],
stdout=subprocess.PIPE)
output, err = p.communicate()
self.assertEqual(p.returncode, 0, output)
results = _split_pretty_table(output)
self.assertEqual(1, len(results), results)
r = results[0]
self.assertIsNotNone(r['UUID'])
self.assertEqual("['debian-normal']", r['Labels'])
self.assertIsNotNone(r['Buildset'])
self.assertEqual('test-hold', r['State'])
self.assertIsNotNone(r['Request Time'])
self.assertEqual('check', r['Pipeline'])
self.assertEqual('check-job', r['Job'])
self.hold_nodeset_requests_in_queue = False
self.executor_server.hold_jobs_in_build = False
self.releaseNodesetRequests()
self.waitUntilSettled()
@simple_layout('layouts/nodepool.yaml', enable_nodepool=True)
def test_nodes_hold_delete(self):
# This tests 3 things (since they are lifecycle related):
# * Setting the node state to hold
# * Setting the node state to delete
# * Deleting the request
self.waitUntilSettled()
request = self.requestNodes(['debian-normal'])
self.assertEqual(request.state,
zuul.model.NodesetRequest.State.FULFILLED)
self.assertEqual(len(request.nodes), 1)
nodes = self.get_url('api/tenant/tenant-one/nodes').json()
self.assertEqual(len(nodes), 1)
token = self._getToken()
p = subprocess.Popen(
['zuul-client',
'--zuul-url', self.base_url, '--auth-token', token,
'node-set-state', '--tenant', 'tenant-one',
nodes[0]['uuid'], 'hold',
],
stdout=subprocess.PIPE)
output, err = p.communicate()
self.assertEqual(p.returncode, 0, output)
self.waitUntilSettled()
node = self.launcher.api.nodes_cache.getItem(nodes[0]['uuid'])
self.assertEqual(node.State.HOLD, node.state)
token = self._getToken()
p = subprocess.Popen(
['zuul-client',
'--zuul-url', self.base_url, '--auth-token', token,
'node-set-state', '--tenant', 'tenant-one',
nodes[0]['uuid'], 'used',
],
stdout=subprocess.PIPE)
output, err = p.communicate()
self.assertEqual(p.returncode, 0, output)
self.waitUntilSettled()
requests = self.get_url(
'api/tenant/tenant-one/nodeset-requests').json()
self.assertEqual(len(requests), 1)
self.assertEqual(request.uuid, requests[0]['uuid'])
token = self._getToken()
p = subprocess.Popen(
['zuul-client',
'--zuul-url', self.base_url, '--auth-token', token,
'nodeset-request-delete', '--tenant', 'tenant-one',
requests[0]['uuid'],
],
stdout=subprocess.PIPE)
output, err = p.communicate()
self.assertEqual(p.returncode, 0, output)
self.waitUntilSettled()
ctx = self.createZKContext(None)
for _ in iterate_timeout(10, "request to be deleted"):
try:
request.refresh(ctx)
except NoNodeError:
break
for _ in iterate_timeout(10, "node to be deleted"):
try:
node.refresh(ctx)
except NoNodeError:
break