Porting tests from Amulet to Zaza

Change-Id: I84fe9bbb195871f2e2a9caffc2c32253616ffd33
Func-Test-PR: https://github.com/openstack-charmers/zaza-openstack-tests/pull/240
Closes-Bug: #1828424
This commit is contained in:
Aurelien Lourot 2020-04-02 08:33:30 +02:00
parent 6d6cb5040b
commit cccfdc343a
13 changed files with 204 additions and 536 deletions

View File

@ -1,33 +1,8 @@
# This file is managed centrally. If you find the need to modify this as a
# one-off, please don't. Intead, consult #openstack-charms and ask about
# requirements management in charms via bot-control. Thank you.
charm-tools>=2.4.4
coverage>=3.6
mock>=1.2
flake8>=2.2.4,<=2.4.1
stestr>=2.2.0
requests>=2.18.4
# BEGIN: Amulet OpenStack Charm Helper Requirements
# Liberty client lower constraints
amulet>=1.14.3,<2.0;python_version=='2.7'
bundletester>=0.6.1,<1.0;python_version=='2.7'
aodhclient>=0.1.0
gnocchiclient>=3.1.0,<3.2.0
python-barbicanclient>=4.0.1
python-ceilometerclient>=1.5.0
python-cinderclient>=1.4.0,<5.0.0
python-designateclient>=1.5
python-glanceclient>=1.1.0
python-heatclient>=0.8.0
python-keystoneclient>=1.7.1
python-manilaclient>=1.8.1
python-neutronclient>=3.1.0
python-novaclient>=2.30.1
python-openstackclient>=1.7.0
python-swiftclient>=2.6.0
pika>=0.10.0,<1.0
distro-info
git+https://github.com/juju/charm-helpers.git#egg=charmhelpers
# END: Amulet OpenStack Charm Helper Requirements
pytz # workaround for 14.04 pip/tox
pyudev # for ceph-* charm unit tests (not mocked?)
# This file is managed centrally by release-tools and should not be modified
# within individual charm repos. See the 'global' dir contents for available
# choices of *requirements.txt files for OpenStack Charms:
# https://github.com/openstack-charmers/release-tools
#
# Functional Test Requirements (let Zaza's dependencies solve all dependencies here!)
git+https://github.com/openstack-charmers/zaza.git#egg=zaza
git+https://github.com/openstack-charmers/zaza-openstack-tests.git#egg=zaza.openstack

View File

@ -1,9 +1,13 @@
# Overview
This directory provides Amulet tests to verify basic deployment functionality
This directory provides Zaza tests to verify basic deployment functionality
from the perspective of this charm, its requirements and its features, as
exercised in a subset of the full OpenStack deployment test bundle topology.
For full details on functional testing of OpenStack charms please refer to
the [functional testing](http://docs.openstack.org/developer/charm-guide/testing.html#functional-testing)
section of the OpenStack Charm Guide.
the [functional testing][charm-guide-functional-testing] section of the
OpenStack Charm Guide.
<!-- LINKS -->
[charm-guide-functional-testing]: https://docs.openstack.org/charm-guide/latest/testing.html#functional-testing

View File

@ -1,426 +0,0 @@
import amulet
import barbicanclient.client as barbican_client
import keystoneclient.exceptions
from charmhelpers.contrib.openstack.amulet.deployment import (
OpenStackAmuletDeployment
)
from charmhelpers.contrib.openstack.amulet.utils import (
OpenStackAmuletUtils,
DEBUG,
)
# Use DEBUG to turn on debug logging
u = OpenStackAmuletUtils(DEBUG)
class BarbicanBasicDeployment(OpenStackAmuletDeployment):
"""Amulet tests on a basic Barbican deployment."""
def __init__(self, series, openstack=None, source=None, stable=False):
"""Deploy the entire test environment.
"""
super(BarbicanBasicDeployment, self).__init__(
series, openstack, source, stable)
self._add_services()
self._add_relations()
self._configure_services()
self._deploy()
u.log.info('Waiting on extended status checks...')
exclude_services = []
self._auto_wait_for_status(exclude_services=exclude_services)
self.d.sentry.wait()
self._initialize_tests()
def _add_services(self):
"""Add services
Add the services that we're testing, where barbican is local,
and the rest of the service are from lp branches that are
compatible with the local charm (e.g. stable or next).
"""
this_service = {'name': 'barbican'}
other_services = [
{'name': 'percona-cluster', 'constraints': {'mem': '3072M'}},
{'name': 'rabbitmq-server'},
{'name': 'keystone'}
]
super(BarbicanBasicDeployment, self)._add_services(
this_service, other_services)
def _add_relations(self):
"""Add all of the relations for the services."""
relations = {
'barbican:shared-db': 'percona-cluster:shared-db',
'barbican:amqp': 'rabbitmq-server:amqp',
'barbican:identity-service': 'keystone:identity-service',
'keystone:shared-db': 'percona-cluster:shared-db',
}
super(BarbicanBasicDeployment, self)._add_relations(relations)
def _configure_services(self):
"""Configure all of the services."""
keystone_config = {
'admin-password': 'openstack',
}
# say we don't need an HSM for these tests
barbican_config = {
'require-hsm-plugin': False,
}
pxc_config = {
'dataset-size': '25%',
'max-connections': 1000,
'root-password': 'ChangeMe123',
'sst-password': 'ChangeMe123',
}
configs = {
'keystone': keystone_config,
'barbican': barbican_config,
'percona-cluster': pxc_config,
}
super(BarbicanBasicDeployment, self)._configure_services(configs)
def _initialize_tests(self):
"""Perform final initialization before tests get run."""
# Access the sentries for inspecting service units
self.barbican_sentry = self.d.sentry['barbican'][0]
self.pxc_sentry = self.d.sentry['percona-cluster'][0]
self.keystone_sentry = self.d.sentry['keystone'][0]
self.rabbitmq_sentry = self.d.sentry['rabbitmq-server'][0]
u.log.debug('openstack release val: {}'.format(
self._get_openstack_release()))
u.log.debug('openstack release str: {}'.format(
self._get_openstack_release_string()))
# Authenticate admin with keystone
self.keystone_session, self.keystone = u.get_default_keystone_session(
self.keystone_sentry,
openstack_release=self._get_openstack_release(),
api_version=3)
def test_100_services(self):
"""Verify the expected services are running on the corresponding
service units."""
u.log.debug('Checking system services on units...')
barbican_svcs = [
'apache2', 'barbican-worker',
]
service_names = {
self.barbican_sentry: barbican_svcs,
}
ret = u.validate_services_by_name(service_names)
if ret:
amulet.raise_status(amulet.FAIL, msg=ret)
u.log.debug('OK')
def test_110_service_catalog(self):
"""Verify that the service catalog endpoint data is valid."""
u.log.debug('Checking keystone service catalog data...')
actual = self.keystone.service_catalog.get_endpoints()
# v3 endpoint check
endpoint_check = [
{
'id': u.not_null,
'interface': interface,
'region': 'RegionOne',
'region_id': 'RegionOne',
'url': u.valid_url,
}
for interface in ('admin', 'public', 'internal')]
validate_catalog = u.validate_v3_svc_catalog_endpoint_data
expected = {
'key-manager': endpoint_check,
'identity': endpoint_check,
}
ret = validate_catalog(expected, actual)
if ret:
amulet.raise_status(amulet.FAIL, msg=ret)
u.log.debug('OK')
def test_114_barbican_api_endpoint(self):
"""Verify the barbican api endpoint data."""
u.log.debug('Checking barbican api endpoint data...')
endpoints = self.keystone.endpoints.list()
u.log.debug(endpoints)
admin_port = '9312'
internal_port = public_port = '9311'
# For keystone v3 it's slightly different.
expected = {'id': u.not_null,
'region': 'RegionOne',
'region_id': 'RegionOne',
'url': u.valid_url,
'interface': u.not_null, # we match this in the test
'service_id': u.not_null}
ret = u.validate_v3_endpoint_data(
endpoints, admin_port, internal_port, public_port, expected)
if ret:
message = 'barbican endpoint: {}'.format(ret)
amulet.raise_status(amulet.FAIL, msg=message)
u.log.debug('OK')
def test_200_barbican_identity_relation(self):
"""Verify the barbican to keystone identity-service relation data"""
u.log.debug('Checking barbican to keystone identity-service '
'relation data...')
unit = self.barbican_sentry
relation = ['identity-service', 'keystone:identity-service']
barbican_ip = unit.relation(*relation)['private-address']
barbican_admin_endpoint = "http://%s:9312" % (barbican_ip)
barbican_endpoint = "http://%s:9311" % (barbican_ip)
expected = {
'admin_url': barbican_admin_endpoint,
'internal_url': barbican_endpoint,
'private-address': barbican_ip,
'public_url': barbican_endpoint,
'region': 'RegionOne',
'service': 'barbican',
}
ret = u.validate_relation_data(unit, relation, expected)
if ret:
message = u.relation_error('barbican identity-service', ret)
amulet.raise_status(amulet.FAIL, msg=message)
u.log.debug('OK')
def test_201_keystone_barbican_identity_relation(self):
"""Verify the keystone to barbican identity-service relation data"""
u.log.debug('Checking keystone:barbican identity relation data...')
unit = self.keystone_sentry
relation = ['identity-service', 'barbican:identity-service']
id_relation = unit.relation(*relation)
id_ip = id_relation['private-address']
expected = {
'auth_host': id_ip,
'auth_port': "35357",
'auth_protocol': 'http',
'private-address': id_ip,
'service_host': id_ip,
'service_password': u.not_null,
'service_port': "5000",
'service_protocol': 'http',
'service_tenant': 'services',
'service_tenant_id': u.not_null,
'service_username': 'barbican',
}
ret = u.validate_relation_data(unit, relation, expected)
if ret:
message = u.relation_error('keystone identity-service', ret)
amulet.raise_status(amulet.FAIL, msg=message)
u.log.debug('OK')
def test_203_barbican_amqp_relation(self):
"""Verify the barbican to rabbitmq-server amqp relation data"""
u.log.debug('Checking barbican:rabbitmq amqp relation data...')
unit = self.barbican_sentry
relation = ['amqp', 'rabbitmq-server:amqp']
expected = {
'username': 'barbican',
'private-address': u.valid_ip,
'vhost': 'openstack'
}
ret = u.validate_relation_data(unit, relation, expected)
if ret:
message = u.relation_error('barbican amqp', ret)
amulet.raise_status(amulet.FAIL, msg=message)
u.log.debug('OK')
def test_204_barbican_amqp_relation(self):
"""Verify the rabbitmq-server to barbican amqp relation data"""
u.log.debug('Checking rabbitmq:barbican barbican relation data...')
unit = self.rabbitmq_sentry
relation = ['amqp', 'barbican:amqp']
expected = {
'hostname': u.valid_ip,
'private-address': u.valid_ip,
'password': u.not_null,
}
ret = u.validate_relation_data(unit, relation, expected)
if ret:
message = u.relation_error('rabbitmq barbican', ret)
amulet.raise_status(amulet.FAIL, msg=message)
u.log.debug('OK')
@staticmethod
def _find_or_create(items, key, create):
"""Find or create the thing in the items
:param items: the items to search using the key
:param key: a function that key(item) -> boolean if found.
:param create: a function to call if the key() never was true.
:returns: the item that was either found or created.
"""
for i in items:
if key(i):
return i
return create()
def test_400_api_connection(self):
"""Simple api calls to check service is up and responding"""
u.log.debug('Checking api functionality...')
# This handles both keystone v2 and v3.
# For keystone v2 we need a user:
# - 'demo' user
# - has a project 'demo'
# - in the 'demo' project
# - with an 'admin' role
# For keystone v3 we need a user:
# - 'default' domain
# - 'demo' user
# - 'demo' project
# - 'admin' role -- to be able to delete.
# barbican requires a user with creator or admin role on the project
# when creating a secret (which this test does). Therefore, we create
# a demo user, demo project, and then get a demo barbican client and do
# the secret. ensure that the default domain is created.
# find or create the 'default' domain
domain = self._find_or_create(
items=self.keystone.domains.list(),
key=lambda u: u.name.lower() == 'default',
create=lambda: self.keystone.domains.create(
"default",
description="domain for barbican testing",
enabled=True))
# find or create the 'demo' user
demo_user = self._find_or_create(
items=self.keystone.users.list(domain=domain.id),
key=lambda u: u.name == 'demo',
create=lambda: self.keystone.users.create(
'demo',
domain=domain.id,
description="Demo user for barbican tests",
enabled=True,
email="demo@example.com",
password="pass"))
# find or create the 'demo' project
demo_project = self._find_or_create(
items=self.keystone.projects.list(domain=domain.id),
key=lambda x: x.name == 'demo',
create=lambda: self.keystone.projects.create(
'demo',
domain=domain.id,
description='barbican testing project',
enabled=True))
# create the role for the user - needs to be admin so that the
# secret can be deleted - note there is only one admin role, and it
# should already be created - if not, then this will fail later.
admin_role = self._find_or_create(
items=self.keystone.roles.list(),
key=lambda r: r.name.lower() == 'admin',
create=lambda: None)
# now grant the creator role to the demo user.
try:
self.keystone.roles.check(
role=admin_role,
user=demo_user,
project=demo_project)
except keystoneclient.exceptions.NotFound:
# create it if it isn't found
self.keystone.roles.grant(
role=admin_role,
user=demo_user,
project=demo_project)
keystone_ip = self.keystone_sentry.info['public-address']
self.keystone_demo = u.authenticate_keystone(
keystone_ip, demo_user.name, 'pass', api_version=3,
user_domain_name=domain.name,
project_domain_name=domain.name,
project_name=demo_project.name)
# Authenticate admin with barbican endpoint
barbican_ep = self.keystone.service_catalog.url_for(
service_type='key-manager', interface='publicURL')
barbican = barbican_client.Client(session=self.keystone_demo.session,
endpoint=barbican_ep)
# now create the secret.
my_secret = barbican.secrets.create()
my_secret.name = u'Random plain text password'
my_secret.payload = u'password'
my_secret_ref = my_secret.store()
assert(my_secret_ref is not None)
# and now delete the secret
my_secret.delete()
u.log.debug('OK')
def test_900_restart_on_config_change(self):
"""Verify that the specified services are restarted when the config
is changed.
"""
sentry = self.barbican_sentry
juju_service = 'barbican'
# Expected default and alternate values
set_default = {'debug': 'False'}
set_alternate = {'debug': 'True'}
# Services which are expected to restart upon config change,
# and corresponding config files affected by the change
conf_file = '/etc/barbican/barbican.conf'
services = {
'barbican-worker': conf_file,
}
# Make config change, check for service restarts
u.log.debug('Making config change on {}...'.format(juju_service))
mtime = u.get_sentry_time(sentry)
self.d.configure(juju_service, set_alternate)
sleep_time = 40
for s, conf_file in services.iteritems():
u.log.debug("Checking that service restarted: {}".format(s))
if not u.validate_service_config_changed(sentry, mtime, s,
conf_file,
retry_count=4,
retry_sleep_time=20,
sleep_time=sleep_time):
self.d.configure(juju_service, set_default)
msg = "service {} didn't restart after config change".format(s)
amulet.raise_status(amulet.FAIL, msg=msg)
sleep_time = 0
self.d.configure(juju_service, set_default)
u.log.debug('OK')
def _assert_services(self, should_run):
services = ('barbican-worker', )
u.get_unit_process_ids(
{self.barbican_sentry: services},
expect_success=should_run)
def test_910_pause_and_resume(self):
"""The services can be paused and resumed. """
self._assert_services(should_run=True)
action_id = u.run_action(self.barbican_sentry, "pause")
assert u.wait_on_action(action_id), "Pause action failed."
self._assert_services(should_run=False)
action_id = u.run_action(self.barbican_sentry, "resume")
assert u.wait_on_action(action_id), "Resume action failed"
self._assert_services(should_run=True)
u.log.debug('OK')

View File

@ -0,0 +1,50 @@
series: bionic
machines:
'0':
constraints: mem=3072M
'1':
'2':
'3':
applications:
mysql:
charm: cs:~openstack-charmers-next/percona-cluster
num_units: 1
to:
- '0'
keystone:
charm: cs:~openstack-charmers-next/keystone
num_units: 1
options:
openstack-origin: cloud:bionic-rocky
to:
- '1'
barbican:
series: bionic
charm: ../../../barbican
num_units: 1
options:
openstack-origin: cloud:bionic-rocky
debug: true
to:
- '2'
rabbitmq-server:
charm: cs:~openstack-charmers-next/rabbitmq-server
num_units: 1
to:
- '3'
relations:
- - mysql:shared-db
- keystone:shared-db
- - mysql:shared-db
- barbican:shared-db
- - keystone:identity-service
- barbican:identity-service
- - rabbitmq-server:amqp
- barbican:amqp

View File

@ -0,0 +1,50 @@
series: bionic
machines:
'0':
constraints: mem=3072M
'1':
'2':
'3':
applications:
mysql:
charm: cs:~openstack-charmers-next/percona-cluster
num_units: 1
to:
- '0'
keystone:
charm: cs:~openstack-charmers-next/keystone
num_units: 1
options:
openstack-origin: cloud:bionic-stein
to:
- '1'
barbican:
series: bionic
charm: ../../../barbican
num_units: 1
options:
openstack-origin: cloud:bionic-stein
debug: true
to:
- '2'
rabbitmq-server:
charm: cs:~openstack-charmers-next/rabbitmq-server
num_units: 1
to:
- '3'
relations:
- - mysql:shared-db
- keystone:shared-db
- - mysql:shared-db
- barbican:shared-db
- - keystone:identity-service
- barbican:identity-service
- - rabbitmq-server:amqp
- barbican:amqp

View File

@ -0,0 +1,50 @@
series: bionic
machines:
'0':
constraints: mem=3072M
'1':
'2':
'3':
applications:
mysql:
charm: cs:~openstack-charmers-next/percona-cluster
num_units: 1
to:
- '0'
keystone:
charm: cs:~openstack-charmers-next/keystone
num_units: 1
options:
openstack-origin: cloud:bionic-train
to:
- '1'
barbican:
series: bionic
charm: ../../../barbican
num_units: 1
options:
openstack-origin: cloud:bionic-train
debug: true
to:
- '2'
rabbitmq-server:
charm: cs:~openstack-charmers-next/rabbitmq-server
num_units: 1
to:
- '3'
relations:
- - mysql:shared-db
- keystone:shared-db
- - mysql:shared-db
- barbican:shared-db
- - keystone:identity-service
- barbican:identity-service
- - rabbitmq-server:amqp
- barbican:amqp

View File

@ -1,12 +0,0 @@
#!/usr/bin/env python
"""Amulet tests on a basic barbican deploy on bionic-rocky for keystone v3.
"""
from basic_deployment import BarbicanBasicDeployment
if __name__ == '__main__':
deployment = BarbicanBasicDeployment(series='bionic',
openstack='cloud:bionic-rocky',
source='cloud:bionic-updates/rocky')
deployment.run_tests()

View File

@ -1,12 +0,0 @@
#!/usr/bin/env python
"""Amulet tests on a basic barbican deploy on bionic-stein for keystone v3.
"""
from basic_deployment import BarbicanBasicDeployment
if __name__ == '__main__':
deployment = BarbicanBasicDeployment(series='bionic',
openstack='cloud:bionic-stein',
source='cloud:bionic-stein')
deployment.run_tests()

View File

@ -1,12 +0,0 @@
#!/usr/bin/env python
"""Amulet tests on a basic barbican deploy on bionic-train for keystone v3.
"""
from basic_deployment import BarbicanBasicDeployment
if __name__ == '__main__':
deployment = BarbicanBasicDeployment(series='bionic',
openstack='cloud:bionic-train',
source='cloud:bionic-train')
deployment.run_tests()

View File

@ -1,18 +1,9 @@
# Bootstrap the model if necessary.
bootstrap: True
# Re-use bootstrap node.
reset: True
# Use tox/requirements to drive the venv instead of bundletester's venv feature.
virtualenv: False
# Leave makefile empty, otherwise unit/lint tests will rerun ahead of amulet.
makefile: []
# Do not specify juju PPA sources. Juju is presumed to be pre-installed
# and configured in all test runner environments.
#sources:
# Do not specify or rely on system packages.
#packages:
# Do not specify python packages here. Use test-requirements.txt
# and tox instead. ie. The venv is constructed before bundletester
# is invoked.
#python-packages:
reset_timeout: 600
charm_name: barbican
smoke_bundles:
- bionic-train
gate_bundles:
- bionic-train
- bionic-stein
- bionic-rocky
tests:
- zaza.openstack.charm_tests.barbican.tests.BarbicanTest

View File

@ -1,4 +1,4 @@
# Source charm (with amulet): ./src/tox.ini
# Source charm (with zaza): ./src/tox.ini
# This file is managed centrally by release-tools and should not be modified
# within individual charm repos. See the 'global' dir contents for available
# choices of tox.ini for OpenStack Charms:
@ -15,41 +15,36 @@ skip_missing_interpreters = False
[testenv]
setenv = VIRTUAL_ENV={envdir}
PYTHONHASHSEED=0
CHARM_DIR={envdir}
AMULET_SETUP_TIMEOUT=5400
whitelist_externals = juju
passenv = HOME TERM AMULET_* CS_* OS_* TEST_*
passenv = HOME TERM CS_* OS_* TEST_*
deps = -r{toxinidir}/test-requirements.txt
install_command =
pip install {opts} {packages}
[testenv:pep8]
basepython = python3
deps=charm-tools
commands = charm-proof
[testenv:func-noop]
# DRY RUN - For Debug
basepython = python2.7
basepython = python3
commands =
bundletester -vl DEBUG -r json -o func-results.json --test-pattern "gate-*" -n --no-destroy
functest-run-suite --help
[testenv:func]
# Run all gate tests which are +x (expected to always pass)
basepython = python2.7
basepython = python3
commands =
bundletester -vl DEBUG -r json -o func-results.json --test-pattern "gate-*" --no-destroy
functest-run-suite --keep-model
[testenv:func-smoke]
# Run a specific test as an Amulet smoke test (expected to always pass)
basepython = python2.7
basepython = python3
commands =
bundletester -vl DEBUG -r json -o func-results.json gate-basic-bionic-train --no-destroy
functest-run-suite --keep-model --smoke
[testenv:func-dev]
# Run all development test targets which are +x (may not always pass!)
basepython = python2.7
[testenv:func-target]
basepython = python3
commands =
bundletester -vl DEBUG -r json -o func-results.json --test-pattern "dev-*" --no-destroy
functest-run-suite --keep-model --bundle {posargs}
[testenv:venv]
commands = {posargs}

View File

@ -1,6 +1,7 @@
# This file is managed centrally. If you find the need to modify this as a
# one-off, please don't. Intead, consult #openstack-charms and ask about
# requirements management in charms via bot-control. Thank you.
# This file is managed centrally by release-tools and should not be modified
# within individual charm repos. See the 'global' dir contents for available
# choices of *requirements.txt files for OpenStack Charms:
# https://github.com/openstack-charmers/release-tools
#
# Lint and unit test requirements
flake8>=2.2.4,<=2.4.1
@ -11,3 +12,12 @@ mock>=1.2
nose>=1.3.7
coverage>=3.6
git+https://github.com/openstack/charms.openstack.git#egg=charms.openstack
#
# Revisit for removal / mock improvement:
netifaces # vault
psycopg2-binary # vault
tenacity # vault
pbr # vault
cryptography # vault, keystone-saml-mellon
lxml # keystone-saml-mellon
hvac # vault, barbican-vault

View File

@ -50,6 +50,11 @@ basepython = python3.7
deps = -r{toxinidir}/test-requirements.txt
commands = stestr run --slowest {posargs}
[testenv:py38]
basepython = python3.8
deps = -r{toxinidir}/test-requirements.txt
commands = stestr run --slowest {posargs}
[testenv:pep8]
basepython = python3
deps = -r{toxinidir}/test-requirements.txt