From 78e0326cadc400e8e4cf131ed440612c892503d4 Mon Sep 17 00:00:00 2001 From: Ryan Beisner Date: Mon, 29 Jun 2015 14:25:54 +0000 Subject: [PATCH] resync tests/charmhelpers --- tests/charmhelpers/contrib/amulet/utils.py | 84 +++++++++---------- .../contrib/openstack/amulet/deployment.py | 11 +-- .../contrib/openstack/amulet/utils.py | 52 ++++++------ 3 files changed, 72 insertions(+), 75 deletions(-) diff --git a/tests/charmhelpers/contrib/amulet/utils.py b/tests/charmhelpers/contrib/amulet/utils.py index c5fa1ed..3de26af 100644 --- a/tests/charmhelpers/contrib/amulet/utils.py +++ b/tests/charmhelpers/contrib/amulet/utils.py @@ -14,6 +14,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with charm-helpers. If not, see . +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 diff --git a/tests/charmhelpers/contrib/openstack/amulet/deployment.py b/tests/charmhelpers/contrib/openstack/amulet/deployment.py index 84850bd..b01e6cb 100644 --- a/tests/charmhelpers/contrib/openstack/amulet/deployment.py +++ b/tests/charmhelpers/contrib/openstack/amulet/deployment.py @@ -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 diff --git a/tests/charmhelpers/contrib/openstack/amulet/utils.py b/tests/charmhelpers/contrib/openstack/amulet/utils.py index bba8458..03f7927 100644 --- a/tests/charmhelpers/contrib/openstack/amulet/utils.py +++ b/tests/charmhelpers/contrib/openstack/amulet/utils.py @@ -14,6 +14,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with charm-helpers. If not, see . +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))