resync tests/charmhelpers

This commit is contained in:
Ryan Beisner 2015-06-29 13:56:27 +00:00
parent 2bad516fc9
commit a27f7b68ab
4 changed files with 73 additions and 76 deletions

View File

@ -22,7 +22,7 @@ bin/charm_helpers_sync.py:
sync: bin/charm_helpers_sync.py
@$(PYTHON) bin/charm_helpers_sync.py -c charm-helpers-hooks.yaml
# @$(PYTHON) bin/charm_helpers_sync.py -c charm-helpers-tests.yaml
@$(PYTHON) bin/charm_helpers_sync.py -c charm-helpers-tests.yaml
publish: lint
bzr push lp:charms/ceph-radosgw

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 amulet
import ConfigParser
import distro_info
import io
@ -173,6 +174,11 @@ class AmuletUtils(object):
Verify that the specified section of the config file contains
the expected option key:value pairs.
Compare expected dictionary data vs actual dictionary data.
The values in the 'expected' dictionary can be strings, bools, ints,
longs, or can be a function that evaluates a variable and returns a
bool.
"""
self.log.debug('Validating config file data ({} in {} on {})'
'...'.format(section, config_file,
@ -195,20 +201,18 @@ class AmuletUtils(object):
if actual != v:
return "section [{}] {}:{} != expected {}:{}".format(
section, k, actual, k, expected[k])
else:
# handle not_null, valid_ip boolean comparison methods, etc.
if v(actual):
return None
else:
return "section [{}] {}:{} != expected {}:{}".format(
section, k, actual, k, expected[k])
# handle function pointers, such as not_null or valid_ip
elif not v(actual):
return "section [{}] {}:{} != expected {}:{}".format(
section, k, actual, k, expected[k])
return None
def _validate_dict_data(self, expected, actual):
"""Validate dictionary data.
Compare expected dictionary data vs actual dictionary data.
The values in the 'expected' dictionary can be strings, bools, ints,
longs, or can be a function that evaluate a variable and returns a
longs, or can be a function that evaluates a variable and returns a
bool.
"""
self.log.debug('actual: {}'.format(repr(actual)))
@ -219,8 +223,10 @@ class AmuletUtils(object):
if (isinstance(v, six.string_types) or
isinstance(v, bool) or
isinstance(v, six.integer_types)):
# handle explicit values
if v != actual[k]:
return "{}:{}".format(k, actual[k])
# handle function pointers, such as not_null or valid_ip
elif not v(actual[k]):
return "{}:{}".format(k, actual[k])
else:
@ -435,15 +441,13 @@ class AmuletUtils(object):
for cmd in commands:
output, code = sentry_unit.run(cmd)
if code == 0:
msg = ('{} `{}` returned {} '
'(OK)'.format(sentry_unit.info['unit_name'],
cmd, code))
self.log.debug(msg)
self.log.debug('{} `{}` returned {} '
'(OK)'.format(sentry_unit.info['unit_name'],
cmd, code))
else:
msg = ('{} `{}` returned {} '
'{}'.format(sentry_unit.info['unit_name'],
cmd, code, output))
return msg
return ('{} `{}` returned {} '
'{}'.format(sentry_unit.info['unit_name'],
cmd, code, output))
return None
def get_process_id_list(self, sentry_unit, process_name):
@ -460,7 +464,7 @@ class AmuletUtils(object):
msg = ('{} `{}` returned {} '
'{}'.format(sentry_unit.info['unit_name'],
cmd, code, output))
raise RuntimeError(msg)
amulet.raise_status(amulet.FAIL, msg=msg)
return str(output).split()
def get_unit_process_ids(self, unit_processes):
@ -481,47 +485,37 @@ class AmuletUtils(object):
self.log.debug('Actual PIDs: {}'.format(actual))
if len(actual) != len(expected):
msg = ('Unit count mismatch. expected, actual: {}, '
'{} '.format(len(expected), len(actual)))
return msg
return ('Unit count mismatch. expected, actual: {}, '
'{} '.format(len(expected), len(actual)))
for (e_sentry, e_proc_names) in expected.iteritems():
e_sentry_name = e_sentry.info['unit_name']
if e_sentry in actual.keys():
a_proc_names = actual[e_sentry]
else:
msg = ('Expected sentry ({}) not found in actual dict data.'
'{}'.format(e_sentry_name, e_sentry))
return msg
return ('Expected sentry ({}) not found in actual dict data.'
'{}'.format(e_sentry_name, e_sentry))
if len(e_proc_names.keys()) != len(a_proc_names.keys()):
msg = ('Process name count mismatch. expected, actual: {}, '
'{}'.format(len(expected), len(actual)))
return msg
return ('Process name count mismatch. expected, actual: {}, '
'{}'.format(len(expected), len(actual)))
for (e_proc_name, e_pids_length), (a_proc_name, a_pids) in \
zip(e_proc_names.items(), a_proc_names.items()):
if e_proc_name != a_proc_name:
msg = ('Process name mismatch. expected, actual: {}, '
'{}'.format(e_proc_name, a_proc_name))
return msg
return ('Process name mismatch. expected, actual: {}, '
'{}'.format(e_proc_name, a_proc_name))
a_pids_length = len(a_pids)
if e_pids_length != a_pids_length:
msg = ('PID count mismatch. {} ({}) expected, actual: {}, '
'{} ({})'.format(e_sentry_name,
e_proc_name,
e_pids_length,
a_pids_length,
a_pids))
return msg
return ('PID count mismatch. {} ({}) expected, actual: '
'{}, {} ({})'.format(e_sentry_name, e_proc_name,
e_pids_length, a_pids_length,
a_pids))
else:
msg = ('PID check OK: {} {} {}: '
'{}'.format(e_sentry_name,
e_proc_name,
e_pids_length,
a_pids))
self.log.debug(msg)
self.log.debug('PID check OK: {} {} {}: '
'{}'.format(e_sentry_name, e_proc_name,
e_pids_length, a_pids))
return None
def validate_list_of_identical_dicts(self, list_of_dicts):
@ -532,10 +526,8 @@ class AmuletUtils(object):
self.log.debug('Hashes: {}'.format(hashes))
if len(set(hashes)) == 1:
msg = 'Dicts within list are identical'
self.log.debug(msg)
self.log.debug('Dicts within list are identical')
else:
msg = 'Dicts within list are not identical'
return msg
return 'Dicts within list are not identical'
return None

View File

@ -79,9 +79,9 @@ class OpenStackAmuletDeployment(AmuletDeployment):
services.append(this_service)
use_source = ['mysql', 'mongodb', 'rabbitmq-server', 'ceph',
'ceph-osd', 'ceph-radosgw']
# Openstack subordinate charms do not expose an origin option as that
# is controlled by the principle
ignore = ['neutron-openvswitch']
# Most OpenStack subordinate charms do not expose an origin option
# as that is controlled by the principle.
ignore = ['cinder-ceph', 'hacluster', 'neutron-openvswitch']
if self.openstack:
for svc in services:
@ -150,8 +150,9 @@ class OpenStackAmuletDeployment(AmuletDeployment):
return releases[self.series]
def get_ceph_expected_pools(self, radosgw=False):
"""Return a list of expected ceph pools based on Ubuntu-OpenStack
release and whether ceph radosgw is flagged as present or not."""
"""Return a list of expected ceph pools in a ceph + cinder + glance
test scenario, based on OpenStack release and whether ceph radosgw
is flagged as present or not."""
if self._get_openstack_release() >= self.trusty_kilo:
# Kilo or later

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 amulet
import json
import logging
import os
@ -177,6 +178,7 @@ class OpenStackAmuletUtils(AmuletUtils):
def authenticate_cinder_admin(self, keystone_sentry, username,
password, tenant):
"""Authenticates admin user with cinder."""
# NOTE(beisner): cinder python client doesn't accept tokens.
service_ip = \
keystone_sentry.relation('shared-db',
'mysql:shared-db')['private-address']
@ -279,7 +281,7 @@ class OpenStackAmuletUtils(AmuletUtils):
msg='Image status wait')
if not ret:
msg = 'Glance image failed to reach expected state.'
raise RuntimeError(msg)
amulet.raise_status(amulet.FAIL, msg=msg)
# Re-validate new image
self.log.debug('Validating image attributes...')
@ -299,7 +301,7 @@ class OpenStackAmuletUtils(AmuletUtils):
self.log.debug(msg_attr)
else:
msg = ('Volume validation failed, {}'.format(msg_attr))
raise RuntimeError(msg)
amulet.raise_status(amulet.FAIL, msg=msg)
return image
@ -343,7 +345,8 @@ class OpenStackAmuletUtils(AmuletUtils):
self.log.warn('/!\\ DEPRECATION WARNING: use '
'delete_resource instead of delete_instance.')
self.log.debug('Deleting instance ({})...'.format(instance))
return self.delete_resource(nova.servers, instance, msg='nova instance')
return self.delete_resource(nova.servers, instance,
msg='nova instance')
def create_or_get_keypair(self, nova, keypair_name="testkey"):
"""Create a new keypair, or return pointer if it already exists."""
@ -361,8 +364,8 @@ class OpenStackAmuletUtils(AmuletUtils):
def create_cinder_volume(self, cinder, vol_name="demo-vol", vol_size=1,
img_id=None, src_vol_id=None, snap_id=None):
"""Create cinder volume, optionally from a glance image, or
optionally as a clone of an existing volume, or optionally
"""Create cinder volume, optionally from a glance image, OR
optionally as a clone of an existing volume, OR optionally
from a snapshot. Wait for the new volume status to reach
the expected status, validate and return a resource pointer.
@ -373,29 +376,33 @@ class OpenStackAmuletUtils(AmuletUtils):
:param snap_id: optional snapshot id to use
:returns: cinder volume pointer
"""
# Handle parameter input
# Handle parameter input and avoid impossible combinations
if img_id and not src_vol_id and not snap_id:
self.log.debug('Creating cinder volume from glance image '
'({})...'.format(img_id))
# Create volume from image
self.log.debug('Creating cinder volume from glance image...')
bootable = 'true'
elif src_vol_id and not img_id and not snap_id:
# Clone an existing volume
self.log.debug('Cloning cinder volume...')
bootable = cinder.volumes.get(src_vol_id).bootable
elif snap_id and not src_vol_id and not img_id:
# Create volume from snapshot
self.log.debug('Creating cinder volume from snapshot...')
snap = cinder.volume_snapshots.find(id=snap_id)
vol_size = snap.size
snap_vol_id = cinder.volume_snapshots.get(snap_id).volume_id
bootable = cinder.volumes.get(snap_vol_id).bootable
elif not img_id and not src_vol_id and not snap_id:
# Create volume
self.log.debug('Creating cinder volume...')
bootable = 'false'
else:
# Impossible combination of parameters
msg = ('Invalid method use - name:{} size:{} img_id:{} '
'src_vol_id:{} snap_id:{}'.format(vol_name, vol_size,
img_id, src_vol_id,
snap_id))
raise RuntimeError(msg)
amulet.raise_status(amulet.FAIL, msg=msg)
# Create new volume
try:
@ -407,7 +414,7 @@ class OpenStackAmuletUtils(AmuletUtils):
vol_id = vol_new.id
except Exception as e:
msg = 'Failed to create volume: {}'.format(e)
raise RuntimeError(msg)
amulet.raise_status(amulet.FAIL, msg=msg)
# Wait for volume to reach available status
ret = self.resource_reaches_status(cinder.volumes, vol_id,
@ -415,7 +422,7 @@ class OpenStackAmuletUtils(AmuletUtils):
msg="Volume status wait")
if not ret:
msg = 'Cinder volume failed to reach expected state.'
raise RuntimeError(msg)
amulet.raise_status(amulet.FAIL, msg=msg)
# Re-validate new volume
self.log.debug('Validating volume attributes...')
@ -433,7 +440,7 @@ class OpenStackAmuletUtils(AmuletUtils):
self.log.debug(msg_attr)
else:
msg = ('Volume validation failed, {}'.format(msg_attr))
raise RuntimeError(msg)
amulet.raise_status(amulet.FAIL, msg=msg)
return vol_new
@ -514,9 +521,9 @@ class OpenStackAmuletUtils(AmuletUtils):
def get_ceph_osd_id_cmd(self, index):
"""Produce a shell command that will return a ceph-osd id."""
cmd = ("`initctl list | grep 'ceph-osd ' | awk 'NR=={} {{ print $2 }}'"
" | grep -o '[0-9]*'`".format(index + 1))
return cmd
return ("`initctl list | grep 'ceph-osd ' | "
"awk 'NR=={} {{ print $2 }}' | "
"grep -o '[0-9]*'`".format(index + 1))
def get_ceph_pools(self, sentry_unit):
"""Return a dict of ceph pools from a single ceph unit, with
@ -528,7 +535,7 @@ class OpenStackAmuletUtils(AmuletUtils):
msg = ('{} `{}` returned {} '
'{}'.format(sentry_unit.info['unit_name'],
cmd, code, output))
raise RuntimeError(msg)
amulet.raise_status(amulet.FAIL, msg=msg)
# Example output: 0 data,1 metadata,2 rbd,3 cinder,4 glance,
for pool in str(output).split(','):
@ -554,7 +561,7 @@ class OpenStackAmuletUtils(AmuletUtils):
msg = ('{} `{}` returned {} '
'{}'.format(sentry_unit.info['unit_name'],
cmd, code, output))
raise RuntimeError(msg)
amulet.raise_status(amulet.FAIL, msg=msg)
return json.loads(output)
def get_ceph_pool_sample(self, sentry_unit, pool_id=0):
@ -571,10 +578,8 @@ class OpenStackAmuletUtils(AmuletUtils):
obj_count = df['pools'][pool_id]['stats']['objects']
kb_used = df['pools'][pool_id]['stats']['kb_used']
self.log.debug('Ceph {} pool (ID {}): {} objects, '
'{} kb used'.format(pool_name,
pool_id,
obj_count,
kb_used))
'{} kb used'.format(pool_name, pool_id,
obj_count, kb_used))
return pool_name, obj_count, kb_used
def validate_ceph_pool_samples(self, samples, sample_type="resource pool"):
@ -591,9 +596,8 @@ class OpenStackAmuletUtils(AmuletUtils):
original, created, deleted = range(3)
if samples[created] <= samples[original] or \
samples[deleted] >= samples[created]:
msg = ('Ceph {} samples ({}) '
'unexpected.'.format(sample_type, samples))
return msg
return ('Ceph {} samples ({}) '
'unexpected.'.format(sample_type, samples))
else:
self.log.debug('Ceph {} samples (OK): '
'{}'.format(sample_type, samples))