From ff9136ae5abd49a91909fdd788f6cbea90e64f6c Mon Sep 17 00:00:00 2001 From: David Ames Date: Tue, 7 Mar 2017 11:10:22 -0800 Subject: [PATCH] Enable Ocata Amulet Tests - Add Zesty as a supported series to metadata.yaml. - Turn on Xenial-Ocata Amulet test definitions. - Sync charm helpers to get Juju 2.x amulet compatibility. - Keeping Zesty-Ocata Amulet test definitions turned off until the metadata.yaml changes propagate to the charm store. Change-Id: Idf45ef0e0587e6874e29145c7ce1109be7cc4e50 --- charm-helpers-tests.yaml | 1 + hooks/charmhelpers/contrib/network/ip.py | 52 ++++++++++++------ .../contrib/openstack/amulet/utils.py | 3 +- .../charmhelpers/contrib/openstack/context.py | 35 ++++++------ .../charmhelpers/contrib/openstack/neutron.py | 19 ++++--- hooks/charmhelpers/contrib/openstack/utils.py | 40 +++++++++++--- hooks/charmhelpers/core/host.py | 2 + .../charmhelpers/core/host_factory/centos.py | 16 ++++++ .../charmhelpers/core/host_factory/ubuntu.py | 32 +++++++++++ hooks/charmhelpers/core/strutils.py | 53 +++++++++++++++++++ hooks/cinder_contexts.py | 3 +- metadata.yaml | 1 + tests/basic_deployment.py | 36 +++++++++---- .../contrib/openstack/amulet/utils.py | 3 +- tests/charmhelpers/core/host.py | 2 + .../charmhelpers/core/host_factory/centos.py | 16 ++++++ .../charmhelpers/core/host_factory/ubuntu.py | 32 +++++++++++ tests/charmhelpers/core/strutils.py | 53 +++++++++++++++++++ tests/charmhelpers/osplatform.py | 25 +++++++++ tests/gate-basic-xenial-ocata | 0 tox.ini | 2 +- 21 files changed, 366 insertions(+), 60 deletions(-) create mode 100644 tests/charmhelpers/osplatform.py mode change 100644 => 100755 tests/gate-basic-xenial-ocata diff --git a/charm-helpers-tests.yaml b/charm-helpers-tests.yaml index 883089d..7e150c1 100644 --- a/charm-helpers-tests.yaml +++ b/charm-helpers-tests.yaml @@ -4,3 +4,4 @@ include: - core - contrib.amulet - contrib.openstack.amulet + - osplatform diff --git a/hooks/charmhelpers/contrib/network/ip.py b/hooks/charmhelpers/contrib/network/ip.py index 54c76a7..7451af9 100644 --- a/hooks/charmhelpers/contrib/network/ip.py +++ b/hooks/charmhelpers/contrib/network/ip.py @@ -31,6 +31,7 @@ from charmhelpers.core.hookenv import ( from charmhelpers.core.host import ( lsb_release, + CompareHostReleases, ) try: @@ -67,6 +68,24 @@ def no_ip_found_error_out(network): raise ValueError(errmsg) +def _get_ipv6_network_from_address(address): + """Get an netaddr.IPNetwork for the given IPv6 address + :param address: a dict as returned by netifaces.ifaddresses + :returns netaddr.IPNetwork: None if the address is a link local or loopback + address + """ + if address['addr'].startswith('fe80') or address['addr'] == "::1": + return None + + prefix = address['netmask'].split("/") + if len(prefix) > 1: + netmask = prefix[1] + else: + netmask = address['netmask'] + return netaddr.IPNetwork("%s/%s" % (address['addr'], + netmask)) + + def get_address_in_network(network, fallback=None, fatal=False): """Get an IPv4 or IPv6 address within the network from the host. @@ -100,11 +119,9 @@ def get_address_in_network(network, fallback=None, fatal=False): if network.version == 6 and netifaces.AF_INET6 in addresses: for addr in addresses[netifaces.AF_INET6]: - if not addr['addr'].startswith('fe80'): - cidr = netaddr.IPNetwork("%s/%s" % (addr['addr'], - addr['netmask'])) - if cidr in network: - return str(cidr.ip) + cidr = _get_ipv6_network_from_address(addr) + if cidr and cidr in network: + return str(cidr.ip) if fallback is not None: return fallback @@ -180,18 +197,18 @@ def _get_for_address(address, key): if address.version == 6 and netifaces.AF_INET6 in addresses: for addr in addresses[netifaces.AF_INET6]: - if not addr['addr'].startswith('fe80'): - network = netaddr.IPNetwork("%s/%s" % (addr['addr'], - addr['netmask'])) - cidr = network.cidr - if address in cidr: - if key == 'iface': - return iface - elif key == 'netmask' and cidr: - return str(cidr).split('/')[1] - else: - return addr[key] + network = _get_ipv6_network_from_address(addr) + if not network: + continue + cidr = network.cidr + if address in cidr: + if key == 'iface': + return iface + elif key == 'netmask' and cidr: + return str(cidr).split('/')[1] + else: + return addr[key] return None @@ -521,7 +538,8 @@ def port_has_listener(address, port): def assert_charm_supports_ipv6(): """Check whether we are able to support charms ipv6.""" - if lsb_release()['DISTRIB_CODENAME'].lower() < "trusty": + release = lsb_release()['DISTRIB_CODENAME'].lower() + if CompareHostReleases(release) < "trusty": raise Exception("IPv6 is not supported in the charms for Ubuntu " "versions less than Trusty 14.04") diff --git a/hooks/charmhelpers/contrib/openstack/amulet/utils.py b/hooks/charmhelpers/contrib/openstack/amulet/utils.py index 1f4cf42..346e6fe 100644 --- a/hooks/charmhelpers/contrib/openstack/amulet/utils.py +++ b/hooks/charmhelpers/contrib/openstack/amulet/utils.py @@ -40,6 +40,7 @@ from charmhelpers.contrib.amulet.utils import ( AmuletUtils ) from charmhelpers.core.decorators import retry_on_exception +from charmhelpers.core.host import CompareHostReleases DEBUG = logging.DEBUG ERROR = logging.ERROR @@ -1255,7 +1256,7 @@ class OpenStackAmuletUtils(AmuletUtils): contents = self.file_contents_safe(sentry_unit, '/etc/memcached.conf', fatal=True) ubuntu_release, _ = self.run_cmd_unit(sentry_unit, 'lsb_release -cs') - if ubuntu_release <= 'trusty': + if CompareHostReleases(ubuntu_release) <= 'trusty': memcache_listen_addr = 'ip6-localhost' else: memcache_listen_addr = '::1' diff --git a/hooks/charmhelpers/contrib/openstack/context.py b/hooks/charmhelpers/contrib/openstack/context.py index 6cdbbbb..7876145 100644 --- a/hooks/charmhelpers/contrib/openstack/context.py +++ b/hooks/charmhelpers/contrib/openstack/context.py @@ -59,6 +59,7 @@ from charmhelpers.core.host import ( write_file, pwgen, lsb_release, + CompareHostReleases, ) from charmhelpers.contrib.hahelpers.cluster import ( determine_apache_port, @@ -155,7 +156,8 @@ class OSContextGenerator(object): if self.missing_data: self.complete = False - log('Missing required data: %s' % ' '.join(self.missing_data), level=INFO) + log('Missing required data: %s' % ' '.join(self.missing_data), + level=INFO) else: self.complete = True return self.complete @@ -213,8 +215,9 @@ class SharedDBContext(OSContextGenerator): hostname_key = "{}_hostname".format(self.relation_prefix) else: hostname_key = "hostname" - access_hostname = get_address_in_network(access_network, - unit_get('private-address')) + access_hostname = get_address_in_network( + access_network, + unit_get('private-address')) set_hostname = relation_get(attribute=hostname_key, unit=local_unit()) if set_hostname != access_hostname: @@ -308,7 +311,10 @@ def db_ssl(rdata, ctxt, ssl_dir): class IdentityServiceContext(OSContextGenerator): - def __init__(self, service=None, service_user=None, rel_name='identity-service'): + def __init__(self, + service=None, + service_user=None, + rel_name='identity-service'): self.service = service self.service_user = service_user self.rel_name = rel_name @@ -457,19 +463,17 @@ class AMQPContext(OSContextGenerator): host = format_ipv6_addr(host) or host rabbitmq_hosts.append(host) - ctxt['rabbitmq_hosts'] = ','.join(sorted(rabbitmq_hosts)) + rabbitmq_hosts = sorted(rabbitmq_hosts) + ctxt['rabbitmq_hosts'] = ','.join(rabbitmq_hosts) transport_hosts = rabbitmq_hosts if transport_hosts: - transport_url_hosts = '' - for host in transport_hosts: - if transport_url_hosts: - format_string = ",{}:{}@{}:{}" - else: - format_string = "{}:{}@{}:{}" - transport_url_hosts += format_string.format( - ctxt['rabbitmq_user'], ctxt['rabbitmq_password'], - host, rabbitmq_port) + transport_url_hosts = ','.join([ + "{}:{}@{}:{}".format(ctxt['rabbitmq_user'], + ctxt['rabbitmq_password'], + host_, + rabbitmq_port) + for host_ in transport_hosts]) ctxt['transport_url'] = "rabbit://{}/{}".format( transport_url_hosts, vhost) @@ -1601,7 +1605,8 @@ class MemcacheContext(OSContextGenerator): if ctxt['use_memcache']: # Trusty version of memcached does not support ::1 as a listen # address so use host file entry instead - if lsb_release()['DISTRIB_CODENAME'].lower() > 'trusty': + release = lsb_release()['DISTRIB_CODENAME'].lower() + if CompareHostReleases(release) > 'trusty': ctxt['memcache_server'] = '::1' else: ctxt['memcache_server'] = 'ip6-localhost' diff --git a/hooks/charmhelpers/contrib/openstack/neutron.py b/hooks/charmhelpers/contrib/openstack/neutron.py index a8f1ed7..37fa0eb 100644 --- a/hooks/charmhelpers/contrib/openstack/neutron.py +++ b/hooks/charmhelpers/contrib/openstack/neutron.py @@ -23,7 +23,10 @@ from charmhelpers.core.hookenv import ( ERROR, ) -from charmhelpers.contrib.openstack.utils import os_release +from charmhelpers.contrib.openstack.utils import ( + os_release, + CompareOpenStackReleases, +) def headers_package(): @@ -198,7 +201,8 @@ def neutron_plugins(): }, 'plumgrid': { 'config': '/etc/neutron/plugins/plumgrid/plumgrid.ini', - 'driver': 'neutron.plugins.plumgrid.plumgrid_plugin.plumgrid_plugin.NeutronPluginPLUMgridV2', + 'driver': ('neutron.plugins.plumgrid.plumgrid_plugin' + '.plumgrid_plugin.NeutronPluginPLUMgridV2'), 'contexts': [ context.SharedDBContext(user=config('database-user'), database=config('database'), @@ -225,7 +229,7 @@ def neutron_plugins(): 'server_services': ['neutron-server'] } } - if release >= 'icehouse': + if CompareOpenStackReleases(release) >= 'icehouse': # NOTE: patch in ml2 plugin for icehouse onwards plugins['ovs']['config'] = '/etc/neutron/plugins/ml2/ml2_conf.ini' plugins['ovs']['driver'] = 'neutron.plugins.ml2.plugin.Ml2Plugin' @@ -233,10 +237,10 @@ def neutron_plugins(): 'neutron-plugin-ml2'] # NOTE: patch in vmware renames nvp->nsx for icehouse onwards plugins['nvp'] = plugins['nsx'] - if release >= 'kilo': + if CompareOpenStackReleases(release) >= 'kilo': plugins['midonet']['driver'] = ( 'neutron.plugins.midonet.plugin.MidonetPluginV2') - if release >= 'liberty': + if CompareOpenStackReleases(release) >= 'liberty': plugins['midonet']['driver'] = ( 'midonet.neutron.plugin_v1.MidonetPluginV2') plugins['midonet']['server_packages'].remove( @@ -244,10 +248,11 @@ def neutron_plugins(): plugins['midonet']['server_packages'].append( 'python-networking-midonet') plugins['plumgrid']['driver'] = ( - 'networking_plumgrid.neutron.plugins.plugin.NeutronPluginPLUMgridV2') + 'networking_plumgrid.neutron.plugins' + '.plugin.NeutronPluginPLUMgridV2') plugins['plumgrid']['server_packages'].remove( 'neutron-plugin-plumgrid') - if release >= 'mitaka': + if CompareOpenStackReleases(release) >= 'mitaka': plugins['nsx']['server_packages'].remove('neutron-plugin-vmware') plugins['nsx']['server_packages'].append('python-vmware-nsx') plugins['nsx']['config'] = '/etc/neutron/nsx.ini' diff --git a/hooks/charmhelpers/contrib/openstack/utils.py b/hooks/charmhelpers/contrib/openstack/utils.py index 7e8ecff..e13450c 100644 --- a/hooks/charmhelpers/contrib/openstack/utils.py +++ b/hooks/charmhelpers/contrib/openstack/utils.py @@ -33,9 +33,7 @@ import yaml from charmhelpers.contrib.network import ip -from charmhelpers.core import ( - unitdata, -) +from charmhelpers.core import unitdata from charmhelpers.core.hookenv import ( action_fail, @@ -55,6 +53,8 @@ from charmhelpers.core.hookenv import ( application_version_set, ) +from charmhelpers.core.strutils import BasicStringComparator + from charmhelpers.contrib.storage.linux.lvm import ( deactivate_lvm_volume_group, is_lvm_physical_volume, @@ -97,6 +97,22 @@ CLOUD_ARCHIVE_KEY_ID = '5EDB1B62EC4926EA' DISTRO_PROPOSED = ('deb http://archive.ubuntu.com/ubuntu/ %s-proposed ' 'restricted main multiverse universe') +OPENSTACK_RELEASES = ( + 'diablo', + 'essex', + 'folsom', + 'grizzly', + 'havana', + 'icehouse', + 'juno', + 'kilo', + 'liberty', + 'mitaka', + 'newton', + 'ocata', + 'pike', +) + UBUNTU_OPENSTACK_RELEASE = OrderedDict([ ('oneiric', 'diablo'), ('precise', 'essex'), @@ -238,6 +254,17 @@ GIT_DEFAULT_BRANCHES = { DEFAULT_LOOPBACK_SIZE = '5G' +class CompareOpenStackReleases(BasicStringComparator): + """Provide comparisons of OpenStack releases. + + Use in the form of + + if CompareOpenStackReleases(release) > 'mitaka': + # do something with mitaka + """ + _list = OPENSTACK_RELEASES + + def error_out(msg): juju_log("FATAL ERROR: %s" % msg, level='ERROR') sys.exit(1) @@ -1066,7 +1093,8 @@ def git_generate_systemd_init_files(templates_dir): shutil.copyfile(init_in_source, init_source) with open(init_source, 'a') as outfile: - template = '/usr/share/openstack-pkg-tools/init-script-template' + template = ('/usr/share/openstack-pkg-tools/' + 'init-script-template') with open(template) as infile: outfile.write('\n\n{}'.format(infile.read())) @@ -1971,9 +1999,7 @@ def enable_memcache(source=None, release=None, package=None): if not _release: _release = get_os_codename_install_source(source) - # TODO: this should be changed to a numeric comparison using a known list - # of releases and comparing by index. - return _release >= 'mitaka' + return CompareOpenStackReleases(_release) >= 'mitaka' def token_cache_pkgs(source=None, release=None): diff --git a/hooks/charmhelpers/core/host.py b/hooks/charmhelpers/core/host.py index 05edfa5..0ee5cb9 100644 --- a/hooks/charmhelpers/core/host.py +++ b/hooks/charmhelpers/core/host.py @@ -45,6 +45,7 @@ if __platform__ == "ubuntu": add_new_group, lsb_release, cmp_pkgrevno, + CompareHostReleases, ) # flake8: noqa -- ignore F401 for this import elif __platform__ == "centos": from charmhelpers.core.host_factory.centos import ( @@ -52,6 +53,7 @@ elif __platform__ == "centos": add_new_group, lsb_release, cmp_pkgrevno, + CompareHostReleases, ) # flake8: noqa -- ignore F401 for this import UPDATEDB_PATH = '/etc/updatedb.conf' diff --git a/hooks/charmhelpers/core/host_factory/centos.py b/hooks/charmhelpers/core/host_factory/centos.py index 902d469..7781a39 100644 --- a/hooks/charmhelpers/core/host_factory/centos.py +++ b/hooks/charmhelpers/core/host_factory/centos.py @@ -2,6 +2,22 @@ import subprocess import yum import os +from charmhelpers.core.strutils import BasicStringComparator + + +class CompareHostReleases(BasicStringComparator): + """Provide comparisons of Host releases. + + Use in the form of + + if CompareHostReleases(release) > 'trusty': + # do something with mitaka + """ + + def __init__(self, item): + raise NotImplementedError( + "CompareHostReleases() is not implemented for CentOS") + def service_available(service_name): # """Determine whether a system service is available.""" diff --git a/hooks/charmhelpers/core/host_factory/ubuntu.py b/hooks/charmhelpers/core/host_factory/ubuntu.py index 8c66af5..0448288 100644 --- a/hooks/charmhelpers/core/host_factory/ubuntu.py +++ b/hooks/charmhelpers/core/host_factory/ubuntu.py @@ -1,5 +1,37 @@ import subprocess +from charmhelpers.core.strutils import BasicStringComparator + + +UBUNTU_RELEASES = ( + 'lucid', + 'maverick', + 'natty', + 'oneiric', + 'precise', + 'quantal', + 'raring', + 'saucy', + 'trusty', + 'utopic', + 'vivid', + 'wily', + 'xenial', + 'yakkety', + 'zesty', +) + + +class CompareHostReleases(BasicStringComparator): + """Provide comparisons of Ubuntu releases. + + Use in the form of + + if CompareHostReleases(release) > 'trusty': + # do something with mitaka + """ + _list = UBUNTU_RELEASES + def service_available(service_name): """Determine whether a system service is available""" diff --git a/hooks/charmhelpers/core/strutils.py b/hooks/charmhelpers/core/strutils.py index dd9b971..685dabd 100644 --- a/hooks/charmhelpers/core/strutils.py +++ b/hooks/charmhelpers/core/strutils.py @@ -68,3 +68,56 @@ def bytes_from_string(value): msg = "Unable to interpret string value '%s' as bytes" % (value) raise ValueError(msg) return int(matches.group(1)) * (1024 ** BYTE_POWER[matches.group(2)]) + + +class BasicStringComparator(object): + """Provides a class that will compare strings from an iterator type object. + Used to provide > and < comparisons on strings that may not necessarily be + alphanumerically ordered. e.g. OpenStack or Ubuntu releases AFTER the + z-wrap. + """ + + _list = None + + def __init__(self, item): + if self._list is None: + raise Exception("Must define the _list in the class definition!") + try: + self.index = self._list.index(item) + except Exception: + raise KeyError("Item '{}' is not in list '{}'" + .format(item, self._list)) + + def __eq__(self, other): + assert isinstance(other, str) or isinstance(other, self.__class__) + return self.index == self._list.index(other) + + def __ne__(self, other): + return not self.__eq__(other) + + def __lt__(self, other): + assert isinstance(other, str) or isinstance(other, self.__class__) + return self.index < self._list.index(other) + + def __ge__(self, other): + return not self.__lt__(other) + + def __gt__(self, other): + assert isinstance(other, str) or isinstance(other, self.__class__) + return self.index > self._list.index(other) + + def __le__(self, other): + return not self.__gt__(other) + + def __str__(self): + """Always give back the item at the index so it can be used in + comparisons like: + + s_mitaka = CompareOpenStack('mitaka') + s_newton = CompareOpenstack('newton') + + assert s_newton > s_mitaka + + @returns: + """ + return self._list[self.index] diff --git a/hooks/cinder_contexts.py b/hooks/cinder_contexts.py index 9e142f3..01088ac 100644 --- a/hooks/cinder_contexts.py +++ b/hooks/cinder_contexts.py @@ -36,7 +36,8 @@ class CephSubordinateContext(OSContextGenerator): if not is_relation_made('ceph', 'key'): return {} service = service_name() - if get_os_codename_package('cinder-common') >= "icehouse": + os_codename = get_os_codename_package('cinder-common') + if os_codename >= "icehouse": volume_driver = 'cinder.volume.drivers.rbd.RBDDriver' else: volume_driver = 'cinder.volume.driver.RBDDriver' diff --git a/metadata.yaml b/metadata.yaml index 843ec50..6d61e2f 100644 --- a/metadata.yaml +++ b/metadata.yaml @@ -12,6 +12,7 @@ tags: - misc series: - xenial + - zesty - trusty - yakkety subordinate: true diff --git a/tests/basic_deployment.py b/tests/basic_deployment.py index 096ff71..cc81413 100644 --- a/tests/basic_deployment.py +++ b/tests/basic_deployment.py @@ -84,8 +84,13 @@ class CinderCephBasicDeployment(OpenStackAmuletDeployment): 'cinder:shared-db': 'percona-cluster:shared-db', 'cinder:identity-service': 'keystone:identity-service', 'cinder:amqp': 'rabbitmq-server:amqp', - 'cinder:ceph': 'ceph:client', } + # If the release is less than ocata then add in the cinder <-> ceph + # relationship; it's not needed for ocata onwards as cinder gained the + # ability to have multiple backends, and in this test (cinder-ceph) we + # only want THIS backend. + if self._get_openstack_release() < self.xenial_ocata: + relations['cinder:ceph'] = 'ceph:client' super(CinderCephBasicDeployment, self)._add_relations(relations) def _configure_services(self): @@ -202,12 +207,18 @@ class CinderCephBasicDeployment(OpenStackAmuletDeployment): def test_102_services(self): """Verify the expected services are running on the service units.""" + if self._get_openstack_release() >= self.xenial_ocata: + cinder_services = ['apache2', + 'cinder-scheduler', + 'cinder-volume'] + else: + cinder_services = ['cinder-api', + 'cinder-scheduler', + 'cinder-volume'] services = { self.rabbitmq_sentry: ['rabbitmq-server'], self.keystone_sentry: ['keystone'], - self.cinder_sentry: ['cinder-api', - 'cinder-scheduler', - 'cinder-volume'], + self.cinder_sentry: cinder_services, } if self._get_openstack_release() < self.xenial_mitaka: @@ -548,15 +559,11 @@ class CinderCephBasicDeployment(OpenStackAmuletDeployment): expected = { 'DEFAULT': { - # XXX: Temporarily disabled use_syslog check, pending - # resolution of https://bugs.launchpad.net/bugs/1604575 - # 'use_syslog': 'False', + 'use_syslog': 'False', 'debug': 'False', 'verbose': 'False', 'iscsi_helper': 'tgtadm', - 'volume_group': 'cinder-volumes', 'auth_strategy': 'keystone', - 'volumes_dir': '/var/lib/cinder/volumes', 'enabled_backends': 'cinder-ceph' }, 'cinder-ceph': { @@ -564,8 +571,11 @@ class CinderCephBasicDeployment(OpenStackAmuletDeployment): 'volume_driver': 'cinder.volume.drivers.rbd.RBDDriver', 'rbd_pool': 'cinder-ceph', 'rbd_user': 'cinder-ceph' - } + }, } + if self._get_openstack_release() < self.xenial_ocata: + expected['DEFAULT']['volume_group'] = 'cinder-volumes' + expected['DEFAULT']['volumes_dir'] = '/var/lib/cinder/volumes' expected_rmq = { 'rabbit_userid': 'cinder', @@ -644,6 +654,11 @@ class CinderCephBasicDeployment(OpenStackAmuletDeployment): if 'cinder-ceph' not in expected_pools: expected_pools.append('cinder-ceph') + if self._get_openstack_release() >= self.xenial_ocata: + # No cinder after mitaka because we don't use the relation in this + # test + expected_pools.remove('cinder') + results = [] sentries = [ self.ceph0_sentry, @@ -740,6 +755,7 @@ class CinderCephBasicDeployment(OpenStackAmuletDeployment): def test_499_ceph_cmds_exit_zero(self): """Check basic functionality of ceph cli commands against all ceph units, and the cinder-ceph unit.""" + u.log.debug('Checking exit values are 0 on ceph commands') sentry_units = [ self.cinder_ceph_sentry, self.ceph0_sentry, diff --git a/tests/charmhelpers/contrib/openstack/amulet/utils.py b/tests/charmhelpers/contrib/openstack/amulet/utils.py index 1f4cf42..346e6fe 100644 --- a/tests/charmhelpers/contrib/openstack/amulet/utils.py +++ b/tests/charmhelpers/contrib/openstack/amulet/utils.py @@ -40,6 +40,7 @@ from charmhelpers.contrib.amulet.utils import ( AmuletUtils ) from charmhelpers.core.decorators import retry_on_exception +from charmhelpers.core.host import CompareHostReleases DEBUG = logging.DEBUG ERROR = logging.ERROR @@ -1255,7 +1256,7 @@ class OpenStackAmuletUtils(AmuletUtils): contents = self.file_contents_safe(sentry_unit, '/etc/memcached.conf', fatal=True) ubuntu_release, _ = self.run_cmd_unit(sentry_unit, 'lsb_release -cs') - if ubuntu_release <= 'trusty': + if CompareHostReleases(ubuntu_release) <= 'trusty': memcache_listen_addr = 'ip6-localhost' else: memcache_listen_addr = '::1' diff --git a/tests/charmhelpers/core/host.py b/tests/charmhelpers/core/host.py index 05edfa5..0ee5cb9 100644 --- a/tests/charmhelpers/core/host.py +++ b/tests/charmhelpers/core/host.py @@ -45,6 +45,7 @@ if __platform__ == "ubuntu": add_new_group, lsb_release, cmp_pkgrevno, + CompareHostReleases, ) # flake8: noqa -- ignore F401 for this import elif __platform__ == "centos": from charmhelpers.core.host_factory.centos import ( @@ -52,6 +53,7 @@ elif __platform__ == "centos": add_new_group, lsb_release, cmp_pkgrevno, + CompareHostReleases, ) # flake8: noqa -- ignore F401 for this import UPDATEDB_PATH = '/etc/updatedb.conf' diff --git a/tests/charmhelpers/core/host_factory/centos.py b/tests/charmhelpers/core/host_factory/centos.py index 902d469..7781a39 100644 --- a/tests/charmhelpers/core/host_factory/centos.py +++ b/tests/charmhelpers/core/host_factory/centos.py @@ -2,6 +2,22 @@ import subprocess import yum import os +from charmhelpers.core.strutils import BasicStringComparator + + +class CompareHostReleases(BasicStringComparator): + """Provide comparisons of Host releases. + + Use in the form of + + if CompareHostReleases(release) > 'trusty': + # do something with mitaka + """ + + def __init__(self, item): + raise NotImplementedError( + "CompareHostReleases() is not implemented for CentOS") + def service_available(service_name): # """Determine whether a system service is available.""" diff --git a/tests/charmhelpers/core/host_factory/ubuntu.py b/tests/charmhelpers/core/host_factory/ubuntu.py index 8c66af5..0448288 100644 --- a/tests/charmhelpers/core/host_factory/ubuntu.py +++ b/tests/charmhelpers/core/host_factory/ubuntu.py @@ -1,5 +1,37 @@ import subprocess +from charmhelpers.core.strutils import BasicStringComparator + + +UBUNTU_RELEASES = ( + 'lucid', + 'maverick', + 'natty', + 'oneiric', + 'precise', + 'quantal', + 'raring', + 'saucy', + 'trusty', + 'utopic', + 'vivid', + 'wily', + 'xenial', + 'yakkety', + 'zesty', +) + + +class CompareHostReleases(BasicStringComparator): + """Provide comparisons of Ubuntu releases. + + Use in the form of + + if CompareHostReleases(release) > 'trusty': + # do something with mitaka + """ + _list = UBUNTU_RELEASES + def service_available(service_name): """Determine whether a system service is available""" diff --git a/tests/charmhelpers/core/strutils.py b/tests/charmhelpers/core/strutils.py index dd9b971..685dabd 100644 --- a/tests/charmhelpers/core/strutils.py +++ b/tests/charmhelpers/core/strutils.py @@ -68,3 +68,56 @@ def bytes_from_string(value): msg = "Unable to interpret string value '%s' as bytes" % (value) raise ValueError(msg) return int(matches.group(1)) * (1024 ** BYTE_POWER[matches.group(2)]) + + +class BasicStringComparator(object): + """Provides a class that will compare strings from an iterator type object. + Used to provide > and < comparisons on strings that may not necessarily be + alphanumerically ordered. e.g. OpenStack or Ubuntu releases AFTER the + z-wrap. + """ + + _list = None + + def __init__(self, item): + if self._list is None: + raise Exception("Must define the _list in the class definition!") + try: + self.index = self._list.index(item) + except Exception: + raise KeyError("Item '{}' is not in list '{}'" + .format(item, self._list)) + + def __eq__(self, other): + assert isinstance(other, str) or isinstance(other, self.__class__) + return self.index == self._list.index(other) + + def __ne__(self, other): + return not self.__eq__(other) + + def __lt__(self, other): + assert isinstance(other, str) or isinstance(other, self.__class__) + return self.index < self._list.index(other) + + def __ge__(self, other): + return not self.__lt__(other) + + def __gt__(self, other): + assert isinstance(other, str) or isinstance(other, self.__class__) + return self.index > self._list.index(other) + + def __le__(self, other): + return not self.__gt__(other) + + def __str__(self): + """Always give back the item at the index so it can be used in + comparisons like: + + s_mitaka = CompareOpenStack('mitaka') + s_newton = CompareOpenstack('newton') + + assert s_newton > s_mitaka + + @returns: + """ + return self._list[self.index] diff --git a/tests/charmhelpers/osplatform.py b/tests/charmhelpers/osplatform.py new file mode 100644 index 0000000..d9a4d5c --- /dev/null +++ b/tests/charmhelpers/osplatform.py @@ -0,0 +1,25 @@ +import platform + + +def get_platform(): + """Return the current OS platform. + + For example: if current os platform is Ubuntu then a string "ubuntu" + will be returned (which is the name of the module). + This string is used to decide which platform module should be imported. + """ + # linux_distribution is deprecated and will be removed in Python 3.7 + # Warings *not* disabled, as we certainly need to fix this. + tuple_platform = platform.linux_distribution() + current_platform = tuple_platform[0] + if "Ubuntu" in current_platform: + return "ubuntu" + elif "CentOS" in current_platform: + return "centos" + elif "debian" in current_platform: + # Stock Python does not detect Ubuntu and instead returns debian. + # Or at least it does in some build environments like Travis CI + return "ubuntu" + else: + raise RuntimeError("This module is not supported on {}." + .format(current_platform)) diff --git a/tests/gate-basic-xenial-ocata b/tests/gate-basic-xenial-ocata old mode 100644 new mode 100755 diff --git a/tox.ini b/tox.ini index d8d8d03..1610be3 100644 --- a/tox.ini +++ b/tox.ini @@ -14,7 +14,7 @@ install_command = pip install --allow-unverified python-apt {opts} {packages} commands = ostestr {posargs} whitelist_externals = juju -passenv = HOME TERM AMULET_* +passenv = HOME TERM AMULET_* CS_API_* [testenv:py27] basepython = python2.7