Updated unit and amulet testing for Murano charm

This commit is contained in:
viswesn 2016-11-12 12:36:18 +05:30
parent 6dc5c7028e
commit 61e025b863
9 changed files with 278 additions and 255 deletions

View File

@ -1,113 +1,88 @@
# Copyright 2016 Canonical Ltd
#
# 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 subprocess
import amulet
import json
import subprocess
import time
import muranoclient.client as murano_client
from keystoneclient import session as keystone_session
from keystoneclient.auth import identity as keystone_identity
import charmhelpers.contrib.openstack.amulet.deployment as amulet_deployment
import charmhelpers.contrib.openstack.amulet.utils as os_amulet_utils
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 = os_amulet_utils.OpenStackAmuletUtils(os_amulet_utils.DEBUG)
u = OpenStackAmuletUtils(DEBUG)
class SDNCharmDeployment(amulet_deployment.OpenStackAmuletDeployment):
"""Amulet tests on a basic sdn_charm deployment."""
class MuranoBasicDeployment(OpenStackAmuletDeployment):
"""Amulet tests on a basic murano deployment."""
def __init__(self, series, openstack=None, source=None, stable=False):
"""Deploy the entire test environment."""
super(SDNCharmDeployment, self).__init__(series, openstack,
source, stable)
super(MuranoBasicDeployment, 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']
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 sdn_charm is local,
Add the services that we're testing, where murano 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': 'sdn_charm'}
this_service = {'name': 'murano'}
other_services = [
{
'name': 'nova-compute',
'constraints': {'mem': '4G'},
},
{
'name': 'neutron-api',
},
{
'name': 'neutron-gateway',
},
{'name': 'mysql'},
{'name': 'percona-cluster', 'constraints': {'mem': '3072M'}},
{'name': 'rabbitmq-server'},
{'name': 'keystone'},
{'name': 'nova-cloud-controller'},
{'name': 'glance'},
]
super(SDNCharmDeployment, self)._add_services(this_service,
other_services)
super(MuranoBasicDeployment, self)._add_services(this_service,
other_services)
def _add_relations(self):
"""Add all of the relations for the services."""
relations = {
'nova-compute:neutron-plugin': 'sdn_charm:neutron-plugin',
'keystone:shared-db': 'mysql:shared-db',
'nova-cloud-controller:shared-db': 'mysql:shared-db',
'nova-cloud-controller:amqp': 'rabbitmq-server:amqp',
'nova-cloud-controller:image-service': 'glance:image-service',
'nova-cloud-controller:identity-service':
'keystone:identity-service',
'nova-compute:cloud-compute':
'nova-cloud-controller:cloud-compute',
'nova-compute:amqp': 'rabbitmq-server:amqp',
'nova-compute:image-service': 'glance:image-service',
'glance:shared-db': 'mysql:shared-db',
'glance:identity-service': 'keystone:identity-service',
'glance:amqp': 'rabbitmq-server:amqp',
'neutron-api:shared-db': 'mysql:shared-db',
'neutron-api:amqp': 'rabbitmq-server:amqp',
'neutron-api:neutron-api': 'nova-cloud-controller:neutron-api',
'neutron-api:identity-service': 'keystone:identity-service',
'neutron-gateway:amqp': 'rabbitmq-server:amqp',
'neutron-gateway:neutron-plugin-api':
'neutron-api:neutron-plugin-api',
'neutron-gateway:quantum-network-service':
'nova-cloud-controller:quantum-network-service',
'neutron-gateway:juju-info': 'sdn_charm:container',
'murano:shared-db': 'percona-cluster:shared-db',
'murano:amqp': 'rabbitmq-server:amqp',
'murano:identity-service': 'keystone:identity-service',
'keystone:shared-db': 'percona-cluster:shared-db',
}
super(SDNCharmDeployment, self)._add_relations(relations)
super(MuranoBasicDeployment, self)._add_relations(relations)
def _configure_services(self):
"""Configure all of the services."""
keystone_config = {'admin-password': 'openstack',
'admin-token': 'ubuntutesting'}
configs = {'keystone': keystone_config}
super(SDNCharmDeployment, self)._configure_services(configs)
keystone_config = {
'admin-password': 'openstack',
'admin-token': 'ubuntutesting'
}
pxc_config = {
'dataset-size': '25%',
'max-connections': 1000,
'root-password': 'ChangeMe123',
'sst-password': 'ChangeMe123',
}
configs = {
'keystone': keystone_config,
'percona-cluster': pxc_config,
}
super(MuranoBasicDeployment, self)._configure_services(configs)
def _get_token(self):
return self.keystone.service_catalog.catalog['token']['id']
@ -115,12 +90,14 @@ class SDNCharmDeployment(amulet_deployment.OpenStackAmuletDeployment):
def _initialize_tests(self):
"""Perform final initialization before tests get run."""
# Access the sentries for inspecting service units
self.sdn_charm_sentry = self.d.sentry['sdn_charm'][0]
self.mysql_sentry = self.d.sentry['mysql'][0]
self.murano_sentry = self.d.sentry['murano'][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]
self.sdn_charm_svcs = [
'sdn_charm-agent', 'sdn_charm-api']
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 endpoint
self.keystone = u.authenticate_keystone_admin(self.keystone_sentry,
@ -128,16 +105,20 @@ class SDNCharmDeployment(amulet_deployment.OpenStackAmuletDeployment):
password='openstack',
tenant='admin')
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))
# Authenticate admin with murano endpoint
murano_ep = self.keystone.service_catalog.url_for(
service_type='application-catalog',
endpoint_type='publicURL')
keystone_ep = self.keystone.service_catalog.url_for(
service_type='identity',
endpoint_type='publicURL')
auth = keystone_identity.V2Token(auth_url=keystone_ep,
token=self.keystone.auth_token)
sess = keystone_session.Session(auth=auth)
self.murano = murano_client.Client(version=1, session=sess,
endpoint_override=murano_ep)
def _run_action(self, unit_id, action, *args):
command = ["juju", "action", "do", "--format=json", unit_id, action]
@ -170,12 +151,217 @@ class SDNCharmDeployment(amulet_deployment.OpenStackAmuletDeployment):
service units."""
u.log.debug('Checking system services on units...')
murano_svcs = [
'murano-api', 'murano-engine'
]
service_names = {
self.sdn_charm_sentry: self.sdn_charm_svcs,
self.murano_sentry: murano_svcs,
}
ret = u.validate_services_by_name(service_names)
if ret:
amulet.raise_status(amulet.FAIL, msg=ret)
u.log.debug('OK')
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...')
endpoint_check = {
'adminURL': u.valid_url,
'id': u.not_null,
'region': 'RegionOne',
'publicURL': u.valid_url,
'internalURL': u.valid_url
}
expected = {
'application-catalog': [endpoint_check],
}
actual = self.keystone.service_catalog.get_endpoints()
ret = u.validate_svc_catalog_endpoint_data(expected, actual)
if ret:
amulet.raise_status(amulet.FAIL, msg=ret)
u.log.debug('OK')
def test_114_murano_api_endpoint(self):
"""Verify the murano api endpoint data."""
u.log.debug('Checking murano api endpoint data...')
endpoints = self.keystone.endpoints.list()
u.log.debug(endpoints)
admin_port = internal_port = public_port = '8082'
expected = {'id': u.not_null,
'region': 'RegionOne',
'adminurl': u.valid_url,
'internalurl': u.valid_url,
'publicurl': u.valid_url,
'service_id': u.not_null}
ret = u.validate_endpoint_data(endpoints, admin_port, internal_port,
public_port, expected)
if ret:
message = 'murano endpoint: {}'.format(ret)
amulet.raise_status(amulet.FAIL, msg=message)
u.log.debug('OK')
def test_200_murano_identity_relation(self):
"""Verify the murano to keystone identity-service relation data"""
u.log.debug('Checking murano to keystone identity-service '
'relation data...')
unit = self.murano_sentry
relation = ['identity-service', 'keystone:identity-service']
murano_relation = unit.relation('identity-service',
'keystone:identity-service')
murano_ip = murano_relation['private-address']
murano_endpoint = "http://%s:8082" % (murano_ip)
expected = {
'admin_url': murano_endpoint,
'internal_url': murano_endpoint,
'private-address': murano_ip,
'public_url': murano_endpoint,
'region': 'RegionOne',
'service': 'murano',
}
ret = u.validate_relation_data(unit, relation, expected)
if ret:
message = u.relation_error('murano identity-service', ret)
amulet.raise_status(amulet.FAIL, msg=message)
u.log.debug('OK')
def test_201_keystone_murano_identity_relation(self):
"""Verify the keystone to murano identity-service relation data"""
u.log.debug('Checking keystone:murano identity relation data...')
unit = self.keystone_sentry
relation = ['identity-service', 'murano:identity-service']
id_relation = unit.relation('identity-service',
'murano:identity-service')
id_ip = id_relation['private-address']
expected = {
'admin_token': 'ubuntutesting',
'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': 'murano',
}
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_murano_amqp_relation(self):
"""Verify the murano to rabbitmq-server amqp relation data"""
u.log.debug('Checking murano:rabbitmq amqp relation data...')
unit = self.murano_sentry
relation = ['amqp', 'rabbitmq-server:amqp']
expected = {
'username': 'murano',
'private-address': u.valid_ip,
'vhost': 'openstack'
}
ret = u.validate_relation_data(unit, relation, expected)
if ret:
message = u.relation_error('murano amqp', ret)
amulet.raise_status(amulet.FAIL, msg=message)
u.log.debug('OK')
def test_204_amqp_murano_relation(self):
"""Verify the rabbitmq-server to murano amqp relation data"""
u.log.debug('Checking rabbitmq:murano amqp relation data...')
unit = self.rabbitmq_sentry
relation = ['amqp', 'murano: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 amqp', ret)
amulet.raise_status(amulet.FAIL, msg=message)
u.log.debug('OK')
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.murano_sentry
juju_service = 'murano'
# 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/murano/murano.conf'
services = {
'murano-api': conf_file,
'murano-engine': 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 _test_910_pause_and_resume(self):
"""The services can be paused and resumed. """
u.log.debug('Checking pause and resume actions...')
unit_name = "murano/0"
unit = self.d.sentry['murano'][0]
juju_service = 'murano'
assert u.status_get(unit)[0] == "active"
action_id = self._run_action(unit_name, "pause")
assert self._wait_on_action(action_id), "Pause action failed."
assert u.status_get(unit)[0] == "maintenance"
# trigger config-changed to ensure that services are still stopped
u.log.debug("Making config change on murano ...")
self.d.configure(juju_service, {'debug': 'True'})
assert u.status_get(unit)[0] == "maintenance"
self.d.configure(juju_service, {'debug': 'False'})
assert u.status_get(unit)[0] == "maintenance"
action_id = self._run_action(unit_name, "resume")
assert self._wait_on_action(action_id), "Resume action failed."
assert u.status_get(unit)[0] == "active"
u.log.debug('OK')

View File

@ -1,23 +0,0 @@
#!/usr/bin/env python
#
# Copyright 2016 Canonical Ltd
#
# 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.
"""Amulet tests on a basic SDN Charm deployment on trusty-icehouse."""
from basic_deployment import SDNCharmDeployment
if __name__ == '__main__':
deployment = SDNCharmDeployment(series='trusty')
deployment.run_tests()

View File

@ -1,25 +0,0 @@
#!/usr/bin/env python
#
# Copyright 2016 Canonical Ltd
#
# 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.
"""Amulet tests on a basic SDN Charm deployment on trusty-liberty."""
from basic_deployment import SDNCharmDeployment
if __name__ == '__main__':
deployment = SDNCharmDeployment(series='trusty',
openstack='cloud:trusty-liberty',
source='cloud:trusty-updates/liberty')
deployment.run_tests()

View File

@ -1,25 +0,0 @@
#!/usr/bin/env python
#
# Copyright 2016 Canonical Ltd
#
# 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.
"""Amulet tests on a basic SDN Charm deployment on trusty-mitaka."""
from basic_deployment import SDNCharmDeployment
if __name__ == '__main__':
deployment = SDNCharmDeployment(series='trusty',
openstack='cloud:trusty-mitaka',
source='cloud:trusty-updates/mitaka')
deployment.run_tests()

View File

@ -14,10 +14,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.
"""Amulet tests on a basic SDN Charm deployment on xenial-mitaka."""
"""Amulet tests on a basic Murano Charm deployment on xenial-mitaka."""
from basic_deployment import SDNCharmDeployment
from basic_deployment import MuranoBasicDeployment
if __name__ == '__main__':
deployment = SDNCharmDeployment(series='xenial')
deployment.run_tests()
deployment = MuranoBasicDeployment(series='xenial')
deployment.run_tests()

View File

@ -4,4 +4,4 @@ os-testr>=0.4.1
charms.reactive
mock>=1.2
coverage>=3.6
git+https://github.com/openstack/charms.openstack.git#egg=charms-openstack
git+https://github.com/openstack/charms.openstack.git#egg=charms-openstack

View File

@ -43,4 +43,4 @@ sys.modules['charmhelpers.fetch'] = charmhelpers.fetch
sys.modules['charmhelpers.cli'] = charmhelpers.cli
sys.modules['charmhelpers.contrib.hahelpers'] = charmhelpers.contrib.hahelpers
sys.modules['charmhelpers.contrib.hahelpers.cluster'] = (
charmhelpers.contrib.hahelpers.cluster)
charmhelpers.contrib.hahelpers.cluster)

View File

@ -1,47 +0,0 @@
# Copyright 2016 Canonical Ltd
#
# 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.
from __future__ import absolute_import
from __future__ import print_function
import unittest
import mock
import charm.openstack.sdn_charm as sdn_charm
class Helper(unittest.TestCase):
def setUp(self):
self._patches = {}
self._patches_start = {}
def tearDown(self):
for k, v in self._patches.items():
v.stop()
setattr(self, k, None)
self._patches = None
self._patches_start = None
def patch(self, obj, attr, return_value=None, **kwargs):
mocked = mock.patch.object(obj, attr, **kwargs)
self._patches[attr] = mocked
started = mocked.start()
started.return_value = return_value
self._patches_start[attr] = started
setattr(self, attr, started)
class TestSDNCharm(Helper):

View File

@ -1,43 +0,0 @@
# Copyright 2016 Canonical Ltd
#
# 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.
from __future__ import absolute_import
from __future__ import print_function
import mock
import reactive.sdn_charm_handlers as handlers
import charms_openstack.test_utils as test_utils
class TestRegisteredHooks(test_utils.TestRegisteredHooks):
def test_hooks(self):
defaults = [
'charm.installed',
'config.changed',
'update-status']
hook_set = {
'when': {
},
'when_not': {
}
}
# test that the hooks were registered via the
# reactive.barbican_handlers
self.registered_hooks_test_helper(handlers, hook_set, defaults)
class TestSDNCharmHandles(test_utils.PatchHelper):