[1chb1n, r=gnuoy] Amulet fixes

This commit is contained in:
Liam Young 2015-10-20 14:07:43 +01:00
commit a559ab7a10
6 changed files with 185 additions and 36 deletions

View File

@ -14,6 +14,7 @@
# You should have received a copy of the GNU Lesser General Public License
# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
import re
import six
from collections import OrderedDict
from charmhelpers.contrib.amulet.deployment import (
@ -114,6 +115,45 @@ class OpenStackAmuletDeployment(AmuletDeployment):
for service, config in six.iteritems(configs):
self.d.configure(service, config)
def _auto_wait_for_status(self, message=None, exclude_services=None,
timeout=1800):
"""Wait for all units to have a specific extended status, except
for any defined as excluded. Unless specified via message, any
status containing any case of 'ready' will be considered a match.
Examples of message usage:
Wait for all unit status to CONTAIN any case of 'ready' or 'ok':
message = re.compile('.*ready.*|.*ok.*', re.IGNORECASE)
Wait for all units to reach this status (exact match):
message = 'Unit is ready'
Wait for all units to reach any one of these (exact match):
message = re.compile('Unit is ready|OK|Ready')
Wait for at least one unit to reach this status (exact match):
message = {'ready'}
See Amulet's sentry.wait_for_messages() for message usage detail.
https://github.com/juju/amulet/blob/master/amulet/sentry.py
:param message: Expected status match
:param exclude_services: List of juju service names to ignore
:param timeout: Maximum time in seconds to wait for status match
:returns: None. Raises if timeout is hit.
"""
if not message:
message = re.compile('.*ready.*', re.IGNORECASE)
if not exclude_services:
exclude_services = []
services = list(set(self.d.services.keys()) - set(exclude_services))
service_messages = {service: message for service in services}
self.d.sentry.wait_for_messages(service_messages, timeout=timeout)
def _get_openstack_release(self):
"""Get openstack release.

View File

@ -566,7 +566,14 @@ def chdir(d):
os.chdir(cur)
def chownr(path, owner, group, follow_links=True):
def chownr(path, owner, group, follow_links=True, chowntopdir=False):
"""
Recursively change user and group ownership of files and directories
in given path. Doesn't chown path itself by default, only its children.
:param bool follow_links: Also Chown links if True
:param bool chowntopdir: Also chown path itself if True
"""
uid = pwd.getpwnam(owner).pw_uid
gid = grp.getgrnam(group).gr_gid
if follow_links:
@ -574,6 +581,10 @@ def chownr(path, owner, group, follow_links=True):
else:
chown = os.lchown
if chowntopdir:
broken_symlink = os.path.lexists(path) and not os.path.exists(path)
if not broken_symlink:
chown(path, uid, gid)
for root, dirs, files in os.walk(path):
for name in dirs + files:
full = os.path.join(root, name)

0
tests/020-basic-trusty-liberty Normal file → Executable file
View File

0
tests/021-basic-wily-liberty Normal file → Executable file
View File

View File

@ -1,10 +1,4 @@
#!/usr/bin/python
import subprocess
"""
Basic ceilometer functional tests.
"""
import amulet
import json
import time
@ -35,6 +29,11 @@ class CeilometerBasicDeployment(OpenStackAmuletDeployment):
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):
@ -49,6 +48,7 @@ class CeilometerBasicDeployment(OpenStackAmuletDeployment):
{'name': 'rabbitmq-server'},
{'name': 'keystone'},
{'name': 'mongodb'},
{'name': 'glance'}, # to satisfy workload status
{'name': 'ceilometer-agent'},
{'name': 'nova-compute'}]
super(CeilometerBasicDeployment, self)._add_services(this_service,
@ -67,7 +67,11 @@ class CeilometerBasicDeployment(OpenStackAmuletDeployment):
'ceilometer-service',
'nova-compute:nova-ceilometer': 'ceilometer-agent:nova-ceilometer',
'nova-compute:shared-db': 'mysql:shared-db',
'nova-compute:amqp': 'rabbitmq-server:amqp'
'nova-compute:amqp': 'rabbitmq-server:amqp',
'glance:identity-service': 'keystone:identity-service',
'glance:shared-db': 'mysql:shared-db',
'glance:amqp': 'rabbitmq-server:amqp',
'nova-compute:image-service': 'glance:image-service'
}
super(CeilometerBasicDeployment, self)._add_relations(relations)
@ -96,9 +100,6 @@ class CeilometerBasicDeployment(OpenStackAmuletDeployment):
u.log.debug('openstack release str: {}'.format(
self._get_openstack_release_string()))
# Let things settle a bit before moving forward
time.sleep(30)
# Authenticate admin with keystone endpoint
self.keystone = u.authenticate_keystone_admin(self.keystone_sentry,
user='admin',
@ -139,6 +140,8 @@ class CeilometerBasicDeployment(OpenStackAmuletDeployment):
def test_100_services(self):
"""Verify the expected services are running on the corresponding
service units."""
u.log.debug('Checking system services on units...')
ceilometer_svcs = [
'ceilometer-agent-central',
'ceilometer-collector',
@ -159,6 +162,8 @@ class CeilometerBasicDeployment(OpenStackAmuletDeployment):
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...')
@ -179,6 +184,8 @@ class CeilometerBasicDeployment(OpenStackAmuletDeployment):
if ret:
amulet.raise_status(amulet.FAIL, msg=ret)
u.log.debug('OK')
def test_112_keystone_api_endpoint(self):
"""Verify the ceilometer api endpoint data."""
u.log.debug('Checking keystone api endpoint data...')
@ -199,6 +206,8 @@ class CeilometerBasicDeployment(OpenStackAmuletDeployment):
message = 'Keystone endpoint: {}'.format(ret)
amulet.raise_status(amulet.FAIL, msg=message)
u.log.debug('OK')
def test_114_ceilometer_api_endpoint(self):
"""Verify the ceilometer api endpoint data."""
u.log.debug('Checking ceilometer api endpoint data...')
@ -218,6 +227,8 @@ class CeilometerBasicDeployment(OpenStackAmuletDeployment):
message = 'Ceilometer endpoint: {}'.format(ret)
amulet.raise_status(amulet.FAIL, msg=message)
u.log.debug('OK')
def test_200_ceilometer_identity_relation(self):
"""Verify the ceilometer to keystone identity-service relation data"""
u.log.debug('Checking ceilometer to keystone identity-service '
@ -243,6 +254,8 @@ class CeilometerBasicDeployment(OpenStackAmuletDeployment):
message = u.relation_error('ceilometer identity-service', ret)
amulet.raise_status(amulet.FAIL, msg=message)
u.log.debug('OK')
def test_201_keystone_ceilometer_identity_relation(self):
"""Verify the keystone to ceilometer identity-service relation data"""
u.log.debug('Checking keystone:ceilometer identity relation data...')
@ -270,20 +283,35 @@ class CeilometerBasicDeployment(OpenStackAmuletDeployment):
message = u.relation_error('keystone identity-service', ret)
amulet.raise_status(amulet.FAIL, msg=message)
u.log.debug('OK')
def test_202_keystone_ceilometer_identity_notes_relation(self):
"""Verify ceilometer to keystone identity-notifications relation"""
u.log.debug('Checking keystone:ceilometer '
'identity-notifications relation data...')
# Relation data may vary depending on timing of hooks and relations.
# May be glance- or keystone- or another endpoint-changed value, so
# check that at least one ???-endpoint-changed value exists.
unit = self.keystone_sentry
relation = ['identity-service', 'ceilometer:identity-notifications']
expected = {
'ceilometer-endpoint-changed': u.not_null,
}
ret = u.validate_relation_data(unit, relation, expected)
if ret:
message = u.relation_error('keystone identity-notifications', ret)
relation_data = unit.relation('identity-service',
'ceilometer:identity-notifications')
expected = '-endpoint-changed'
found = 0
for key in relation_data.keys():
if expected in key and relation_data[key]:
found += 1
u.log.debug('{}: {}'.format(key, relation_data[key]))
if not found:
message = ('keystone:ceilometer identity-notification relation '
'error\n expected something like: {}\n actual: '
'{}'.format(expected, relation_data))
amulet.raise_status(amulet.FAIL, msg=message)
u.log.debug('OK')
def test_203_ceilometer_amqp_relation(self):
"""Verify the ceilometer to rabbitmq-server amqp relation data"""
u.log.debug('Checking ceilometer:rabbitmq amqp relation data...')
@ -300,6 +328,8 @@ class CeilometerBasicDeployment(OpenStackAmuletDeployment):
message = u.relation_error('ceilometer amqp', ret)
amulet.raise_status(amulet.FAIL, msg=message)
u.log.debug('OK')
def test_204_amqp_ceilometer_relation(self):
"""Verify the rabbitmq-server to ceilometer amqp relation data"""
u.log.debug('Checking rabbitmq:ceilometer amqp relation data...')
@ -316,6 +346,8 @@ class CeilometerBasicDeployment(OpenStackAmuletDeployment):
message = u.relation_error('rabbitmq amqp', ret)
amulet.raise_status(amulet.FAIL, msg=message)
u.log.debug('OK')
def test_205_ceilometer_to_mongodb_relation(self):
"""Verify the ceilometer to mongodb relation data"""
u.log.debug('Checking ceilometer:mongodb relation data...')
@ -331,6 +363,8 @@ class CeilometerBasicDeployment(OpenStackAmuletDeployment):
message = u.relation_error('ceilometer shared-db', ret)
amulet.raise_status(amulet.FAIL, msg=message)
u.log.debug('OK')
def test_206_mongodb_to_ceilometer_relation(self):
"""Verify the mongodb to ceilometer relation data"""
u.log.debug('Checking mongodb:ceilometer relation data...')
@ -343,14 +377,13 @@ class CeilometerBasicDeployment(OpenStackAmuletDeployment):
'type': 'database',
}
if self._get_openstack_release() == self.precise_icehouse:
expected['replset'] = 'myset'
ret = u.validate_relation_data(unit, relation, expected)
if ret:
message = u.relation_error('mongodb database', ret)
amulet.raise_status(amulet.FAIL, msg=message)
u.log.debug('OK')
def test_207_ceilometer_ceilometer_agent_relation(self):
"""Verify the ceilometer to ceilometer-agent relation data"""
u.log.debug('Checking ceilometer:ceilometer-agent relation data...')
@ -379,6 +412,8 @@ class CeilometerBasicDeployment(OpenStackAmuletDeployment):
message = u.relation_error('ceilometer-service', ret)
amulet.raise_status(amulet.FAIL, msg=message)
u.log.debug('OK')
def test_208_ceilometer_agent_ceilometer_relation(self):
"""Verify the ceilometer-agent to ceilometer relation data"""
u.log.debug('Checking ceilometer-agent:ceilometer relation data...')
@ -391,6 +426,8 @@ class CeilometerBasicDeployment(OpenStackAmuletDeployment):
message = u.relation_error('ceilometer-service', ret)
amulet.raise_status(amulet.FAIL, msg=message)
u.log.debug('OK')
def test_209_nova_compute_ceilometer_agent_relation(self):
"""Verify the nova-compute to ceilometer relation data"""
u.log.debug('Checking nova-compute:ceilometer relation data...')
@ -425,6 +462,8 @@ class CeilometerBasicDeployment(OpenStackAmuletDeployment):
message = u.relation_error('ceilometer-service', ret)
amulet.raise_status(amulet.FAIL, msg=message)
u.log.debug('OK')
def test_300_ceilometer_config(self):
"""Verify the data in the ceilometer config file."""
u.log.debug('Checking ceilometer config file data...')
@ -480,6 +519,8 @@ class CeilometerBasicDeployment(OpenStackAmuletDeployment):
message = "ceilometer config error: {}".format(ret)
amulet.raise_status(amulet.FAIL, msg=message)
u.log.debug('OK')
def test_301_nova_config(self):
"""Verify data in the nova compute nova config file"""
u.log.debug('Checking nova compute config file...')
@ -528,6 +569,8 @@ class CeilometerBasicDeployment(OpenStackAmuletDeployment):
if ret:
amulet.raise_status(amulet.FAIL, msg=ret)
u.log.debug('OK')
def test_302_nova_ceilometer_config(self):
"""Verify data in the ceilometer config file on the
nova-compute (ceilometer-agent) unit."""
@ -550,11 +593,14 @@ class CeilometerBasicDeployment(OpenStackAmuletDeployment):
message = "ceilometer config error: {}".format(ret)
amulet.raise_status(amulet.FAIL, msg=message)
u.log.debug('OK')
def test_400_api_connection(self):
"""Simple api calls to check service is up and responding"""
u.log.debug('Checking api functionality...')
assert(self.ceil.samples.list() == [])
assert(self.ceil.meters.list() == [])
u.log.debug('OK')
# NOTE(beisner): need to add more functional tests
@ -569,38 +615,49 @@ class CeilometerBasicDeployment(OpenStackAmuletDeployment):
set_default = {'debug': 'False'}
set_alternate = {'debug': 'True'}
# Config file affected by juju set config change
# Services which are expected to restart upon config change,
# and corresponding config files affected by the change
conf_file = '/etc/ceilometer/ceilometer.conf'
services = {
'ceilometer-collector': conf_file,
'ceilometer-api': conf_file,
'ceilometer-alarm-evaluator': conf_file,
'ceilometer-alarm-notifier': conf_file,
'ceilometer-agent-notification': conf_file,
}
# Services which are expected to restart upon config change
services = [
'ceilometer-agent-central',
'ceilometer-collector',
'ceilometer-api',
'ceilometer-alarm-evaluator',
'ceilometer-alarm-notifier',
'ceilometer-agent-notification',
]
if self._get_openstack_release() == self.trusty_liberty or \
self._get_openstack_release() >= self.wily_liberty:
# Liberty and later
services['ceilometer-polling'] = conf_file
else:
# Juno and earlier
services['ceilometer-agent-central'] = 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 in services:
for s, conf_file in services.iteritems():
u.log.debug("Checking that service restarted: {}".format(s))
if not u.service_restarted(sentry, s,
conf_file, sleep_time=sleep_time,
pgrep_full=True):
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_1000_pause_and_resume(self):
def test_910_pause_and_resume(self):
"""The services can be paused and resumed. """
u.log.debug('Checking pause and resume actions...')
unit_name = "ceilometer/0"
unit = self.d.sentry.unit[unit_name]
@ -613,3 +670,4 @@ class CeilometerBasicDeployment(OpenStackAmuletDeployment):
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

@ -14,6 +14,7 @@
# You should have received a copy of the GNU Lesser General Public License
# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
import re
import six
from collections import OrderedDict
from charmhelpers.contrib.amulet.deployment import (
@ -114,6 +115,45 @@ class OpenStackAmuletDeployment(AmuletDeployment):
for service, config in six.iteritems(configs):
self.d.configure(service, config)
def _auto_wait_for_status(self, message=None, exclude_services=None,
timeout=1800):
"""Wait for all units to have a specific extended status, except
for any defined as excluded. Unless specified via message, any
status containing any case of 'ready' will be considered a match.
Examples of message usage:
Wait for all unit status to CONTAIN any case of 'ready' or 'ok':
message = re.compile('.*ready.*|.*ok.*', re.IGNORECASE)
Wait for all units to reach this status (exact match):
message = 'Unit is ready'
Wait for all units to reach any one of these (exact match):
message = re.compile('Unit is ready|OK|Ready')
Wait for at least one unit to reach this status (exact match):
message = {'ready'}
See Amulet's sentry.wait_for_messages() for message usage detail.
https://github.com/juju/amulet/blob/master/amulet/sentry.py
:param message: Expected status match
:param exclude_services: List of juju service names to ignore
:param timeout: Maximum time in seconds to wait for status match
:returns: None. Raises if timeout is hit.
"""
if not message:
message = re.compile('.*ready.*', re.IGNORECASE)
if not exclude_services:
exclude_services = []
services = list(set(self.d.services.keys()) - set(exclude_services))
service_messages = {service: message for service in services}
self.d.sentry.wait_for_messages(service_messages, timeout=timeout)
def _get_openstack_release(self):
"""Get openstack release.