Add functional tests

This commit is contained in:
Liam Young 2016-07-12 17:40:58 +00:00
parent c73067bf5b
commit 1b6c0661dc
11 changed files with 390 additions and 0 deletions

View File

@ -125,6 +125,15 @@ def render_all_configs(interfaces_list):
DesignateBindCharm.singleton.render_with_interfaces(interfaces_list)
def assess_status():
"""Just call the DesignateBindCharm.singleton.assess_status() command
to update status on the unit.
@returns: None
"""
DesignateBindCharm.singleton.assess_status()
class DNSAdapter(adapters.OpenStackRelationAdapter):
def __init__(self, relation):
@ -194,6 +203,7 @@ class DesignateBindCharm(openstack_charm.OpenStackCharm):
default_service = 'bind9'
adapters_class = BindAdapters
release = 'icehouse'
required_relations = []
def __init__(self, release=None, **kwargs):
super(DesignateBindCharm, self).__init__(release='icehouse', **kwargs)

View File

@ -8,6 +8,8 @@ description: |
.
This charm provides BIND9 as a backend for integration with OpenStack
Designate, providing DNSaaS in an OpenStack cloud.
tags:
- openstack
series:
- trusty
- wily

View File

@ -92,3 +92,8 @@ def process_sync_requests(hacluster):
'''If this unit is the leader process and new sync requests'''
if hookenv.is_leader():
designate_bind.process_requests(hacluster)
@reactive.when('zones.initialised')
def assess_status():
designate_bind.assess_status()

2
src/requirements.txt Normal file
View File

@ -0,0 +1,2 @@
flake8
pytest

20
src/test-requirements.txt Normal file
View File

@ -0,0 +1,20 @@
# charm-proof
charm-tools>=2.0.0
# amulet deployment helpers
bzr+lp:charm-helpers#egg=charmhelpers
# BEGIN: Amulet OpenStack Charm Helper Requirements
# Liberty client lower constraints
amulet>=1.14.3,<2.0
bundletester>=0.6.1,<1.0
python-keystoneclient>=1.7.1,<2.0
python-designateclient>=1.5,<2.0
python-cinderclient>=1.4.0,<2.0
python-glanceclient>=1.1.0,<2.0
python-heatclient>=0.8.0,<1.0
python-neutronclient>=3.1.0,<4.0
python-novaclient>=2.30.1,<3.0
python-openstackclient>=1.7.0,<2.0
python-swiftclient>=2.6.0,<3.0
pika>=0.10.0,<1.0
distro-info
# END: Amulet OpenStack Charm Helper Requirements

View File

@ -0,0 +1,261 @@
import amulet
import json
import subprocess
import time
import designateclient.client as designate_client
import designateclient.v1.domains as domains
import designateclient.v1.records as records
import charmhelpers.contrib.openstack.amulet.deployment as amulet_deployment
import charmhelpers.contrib.openstack.amulet.utils as os_amulet_utils
# Use DEBUG to turn on debug logging
u = os_amulet_utils.OpenStackAmuletUtils(os_amulet_utils.DEBUG)
class DesignateBindDeployment(amulet_deployment.OpenStackAmuletDeployment):
"""Amulet tests on a basic designate deployment."""
TEST_DOMAIN = 'amuletexample.com.'
TEST_WWW_RECORD = "www.{}".format(TEST_DOMAIN)
TEST_RECORD = {TEST_WWW_RECORD: '10.0.0.23'}
def __init__(self, series, openstack=None, source=None, stable=False):
"""Deploy the entire test environment."""
super(DesignateBindDeployment, 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 = ['mysql', 'mongodb']
self._auto_wait_for_status(exclude_services=exclude_services)
self._initialize_tests()
def _add_services(self):
"""Add services
Add the services that we're testing, where designate 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': 'designate-bind'}
other_services = [{'name': 'mysql'},
{'name': 'rabbitmq-server'},
{'name': 'keystone'},
{'name': 'designate',
'location': 'cs:~gnuoy/trusty/designate-0'}]
super(DesignateBindDeployment, self)._add_services(this_service,
other_services)
def _add_relations(self):
"""Add all of the relations for the services."""
relations = {
'designate:shared-db': 'mysql:shared-db',
'designate:amqp': 'rabbitmq-server:amqp',
'designate:identity-service': 'keystone:identity-service',
'keystone:shared-db': 'mysql:shared-db',
'designate:dns-backend': 'designate-bind:dns-backend',
}
super(DesignateBindDeployment, self)._add_relations(relations)
def _configure_services(self):
"""Configure all of the services."""
if self.series == 'trusty':
keystone_config = {'admin-password': 'openstack',
'admin-token': 'ubuntutesting',
'openstack-origin': 'cloud:trusty-mitaka'}
designate_config = {'openstack-origin': 'cloud:trusty-mitaka'}
configs = {
'keystone': keystone_config,
'designate': designate_config}
else:
keystone_config = {'admin-password': 'openstack',
'admin-token': 'ubuntutesting'}
configs = {'keystone': keystone_config}
super(DesignateBindDeployment, self)._configure_services(configs)
def _get_token(self):
return self.keystone.service_catalog.catalog['token']['id']
def _initialize_tests(self):
"""Perform final initialization before tests get run."""
# Access the sentries for inspecting service units
self.designate_sentry = self.d.sentry['designate'][0]
self.designate_bind_sentry = self.d.sentry['designate-bind'][0]
self.mysql_sentry = self.d.sentry['mysql'][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()))
self.dns_slave_ip = self.designate_bind_sentry.relation(
'dns-backend',
'designate:dns-backend')['private-address']
self.designate_svcs = [
'designate-agent', 'designate-api', 'designate-central',
'designate-mdns', 'designate-pool-manager', 'designate-sink',
'designate-zone-manager',
]
# Authenticate admin with keystone endpoint
self.keystone = u.authenticate_keystone_admin(self.keystone_sentry,
user='admin',
password='openstack',
tenant='admin')
# Authenticate admin with designate endpoint
designate_ep = self.keystone.service_catalog.url_for(
service_type='dns',
endpoint_type='publicURL')
keystone_ep = self.keystone.service_catalog.url_for(
service_type='identity',
endpoint_type='publicURL')
self.designate = designate_client.Client(
version='1',
auth_url=keystone_ep,
username="admin",
password="openstack",
tenant_name="admin",
endpoint=designate_ep)
def check_and_wait(self, check_command, interval=2, max_wait=200,
desc=None):
waited = 0
while not check_command() or waited > max_wait:
if desc:
u.log.debug(desc)
time.sleep(interval)
waited = waited + interval
if waited > max_wait:
raise Exception('cmd failed {}'.format(check_command))
def _run_action(self, unit_id, action, *args):
command = ["juju", "action", "do", "--format=json", unit_id, action]
command.extend(args)
print("Running command: %s\n" % " ".join(command))
output = subprocess.check_output(command)
output_json = output.decode(encoding="UTF-8")
data = json.loads(output_json)
action_id = data[u'Action queued with id']
return action_id
def _wait_on_action(self, action_id):
command = ["juju", "action", "fetch", "--format=json", action_id]
while True:
try:
output = subprocess.check_output(command)
except Exception as e:
print(e)
return False
output_json = output.decode(encoding="UTF-8")
data = json.loads(output_json)
if data[u"status"] == "completed":
return True
elif data[u"status"] == "failed":
return False
time.sleep(2)
def test_100_services(self):
"""Verify the expected services are running on the corresponding
service units."""
u.log.debug('Checking system services on units...')
service_names = {
self.designate_sentry: self.designate_svcs,
}
ret = u.validate_services_by_name(service_names)
if ret:
amulet.raise_status(amulet.FAIL, msg=ret)
u.log.debug('OK')
def test_205_designate_designate_bind_relation(self):
"""Verify the designate to designate-bind dns-backend relation data"""
u.log.debug('Checking designate:designate-bind dns-backend relation'
'data...')
unit = self.designate_sentry
relation = ['dns-backend', 'designate-bind:dns-backend']
expected = {
'private-address': u.valid_ip,
}
ret = u.validate_relation_data(unit, relation, expected)
if ret:
message = u.relation_error('designate dns-backend', ret)
amulet.raise_status(amulet.FAIL, msg=message)
def test_206_designate_bind_designate_relation(self):
"""Verify the designate_bind to designate dns-backend relation data"""
u.log.debug('Checking designate-bind:designate dns-backend relation'
'data...')
unit = self.designate_bind_sentry
relation = ['dns-backend', 'designate:dns-backend']
expected = {
'private-address': u.valid_ip,
'rndckey': u.not_null,
'algorithm': 'hmac-md5',
}
ret = u.validate_relation_data(unit, relation, expected)
if ret:
message = u.relation_error('designate dns-backend', ret)
amulet.raise_status(amulet.FAIL, msg=message)
def get_domain_id(self, domain_name):
domain_id = None
for dom in self.designate.domains.list():
if dom.name == domain_name:
domain_id = dom.id
return domain_id
def get_test_domain_id(self):
return self.get_domain_id(self.TEST_DOMAIN)
def check_test_domain_gone(self):
return not self.get_test_domain_id()
def check_slave_resolve_test_record(self):
lookup_cmd = [
'dig', '+short', '@{}'.format(self.dns_slave_ip),
self.TEST_WWW_RECORD]
cmd_out = subprocess.check_output(lookup_cmd).rstrip('\r\n')
return self.TEST_RECORD[self.TEST_WWW_RECORD] == cmd_out
def test_400_domain_creation(self):
"""Simple api calls to create domain"""
u.log.debug('Checking if domain exists before trying to create it')
old_dom_id = self.get_test_domain_id()
if old_dom_id:
u.log.debug('Deleting old domain')
self.designate.domains.delete(old_dom_id)
self.check_and_wait(
self.check_test_domain_gone,
desc='Waiting for domain to disappear')
u.log.debug('Creating new domain')
domain = domains.Domain(
name=self.TEST_DOMAIN,
email="fred@amuletexample.com")
new_domain = self.designate.domains.create(domain)
assert(new_domain is not None)
u.log.debug('Creating new test record')
_record = records.Record(
name=self.TEST_WWW_RECORD,
type="A",
data=self.TEST_RECORD[self.TEST_WWW_RECORD])
self.designate.records.create(new_domain.id, _record)
self.check_and_wait(
self.check_slave_resolve_test_record,
desc='Waiting for dns record to propagate')
u.log.debug('Tidy up delete test record')
self.designate.domains.delete(new_domain.id)
u.log.debug('OK')

9
src/tests/gate-basic-trusty Executable file
View File

@ -0,0 +1,9 @@
#!/usr/bin/env python
"""Amulet tests on a basic aodh deployment on trusty-mitaka."""
from basic_deployment import DesignateBindDeployment
if __name__ == '__main__':
deployment = DesignateBindDeployment(series='trusty')
deployment.run_tests()

9
src/tests/gate-basic-xenial Executable file
View File

@ -0,0 +1,9 @@
#!/usr/bin/env python
"""Amulet tests on a basic aodh deployment on mitaka."""
from basic_deployment import DesignateBindDeployment
if __name__ == '__main__':
deployment = DesignateBindDeployment(series='xenial')
deployment.run_tests()

17
src/tests/tests.yaml Normal file
View File

@ -0,0 +1,17 @@
# Bootstrap the model if necessary.
bootstrap: True
# Re-use bootstrap node instead of destroying/re-bootstrapping.
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:

53
src/tox.ini Normal file
View File

@ -0,0 +1,53 @@
[tox]
envlist = pep8
skipsdist = True
[testenv]
envdir = .tox/py27
setenv = VIRTUAL_ENV={envdir}
PYTHONHASHSEED=0
AMULET_SETUP_TIMEOUT=2700
deps = -r{toxinidir}/test-requirements.txt
install_command =
pip install --allow-unverified python-apt {opts} {packages}
[testenv:pep8]
basepython = python2.7
commands = charm-proof
[testenv:func27-noop]
# DRY RUN - For Debug
basepython = python2.7
commands =
bundletester -vl DEBUG -r json -o func-results.json --test-pattern "gate-*" -n --no-destroy
[testenv:func27]
# Charm Functional Test
# Run all gate tests which are +x (expected to always pass)
basepython = python2.7
commands =
bundletester -vl DEBUG -r json -o func-results.json --test-pattern "gate-*" --no-destroy
[testenv:func27-smoke]
# Charm Functional Test
# Run a specific test as an Amulet smoke test (expected to always pass)
basepython = python2.7
commands =
bundletester -vl DEBUG -r json -o func-results.json gate-basic-xenial-mitaka --no-destroy
[testenv:func27-dfs]
# Charm Functional Test
# Run all deploy-from-source tests which are +x (may not always pass!)
basepython = python2.7
commands =
bundletester -vl DEBUG -r json -o func-results.json --test-pattern "dfs-*" --no-destroy
[testenv:func27-dev]
# Charm Functional Test
# Run all development test targets which are +x (may not always pass!)
basepython = python2.7
commands =
bundletester -vl DEBUG -r json -o func-results.json --test-pattern "dev-*" --no-destroy
[testenv:venv]
commands = {posargs}

View File

@ -108,6 +108,7 @@ class TestDesignateHandlers(unittest.TestCase):
('cluster.connected', ),
('zones.initialised', ),
],
'assess_status': [('zones.initialised', )],
}
when_not_patterns = {
'install_packages': [('installed', )],
@ -128,6 +129,7 @@ class TestDesignateHandlers(unittest.TestCase):
(_when_not_args, when_not_patterns)]:
for f, args in t.items():
# check that function is in patterns
print(f)
self.assertTrue(f in p.keys())
# check that the lists are equal
l = [a['args'] for a in args]