From 4c5204ee19b2a7cccb4330ec555437c1a827e03e Mon Sep 17 00:00:00 2001 From: Ryan Beisner Date: Wed, 21 Feb 2018 07:37:16 -0600 Subject: [PATCH] Sync charm-helpers Change-Id: I296f081e9de99da2fefe5a581dfc2ffe5fba4429 --- hooks/charmhelpers/contrib/network/ip.py | 7 +- .../contrib/openstack/amulet/utils.py | 4 +- .../charmhelpers/contrib/openstack/context.py | 86 ++++++++++++++++--- hooks/charmhelpers/core/templating.py | 27 ++++-- .../contrib/openstack/amulet/utils.py | 4 +- tests/charmhelpers/core/templating.py | 27 ++++-- tox.ini | 2 +- 7 files changed, 123 insertions(+), 34 deletions(-) diff --git a/hooks/charmhelpers/contrib/network/ip.py b/hooks/charmhelpers/contrib/network/ip.py index 9483e257..b13277bb 100644 --- a/hooks/charmhelpers/contrib/network/ip.py +++ b/hooks/charmhelpers/contrib/network/ip.py @@ -110,7 +110,12 @@ def get_address_in_network(network, fallback=None, fatal=False): _validate_cidr(network) network = netaddr.IPNetwork(network) for iface in netifaces.interfaces(): - addresses = netifaces.ifaddresses(iface) + try: + addresses = netifaces.ifaddresses(iface) + except ValueError: + # If an instance was deleted between + # netifaces.interfaces() run and now, its interfaces are gone + continue if network.version == 4 and netifaces.AF_INET in addresses: for addr in addresses[netifaces.AF_INET]: cidr = netaddr.IPNetwork("%s/%s" % (addr['addr'], diff --git a/hooks/charmhelpers/contrib/openstack/amulet/utils.py b/hooks/charmhelpers/contrib/openstack/amulet/utils.py index 87f364d1..d93cff3c 100644 --- a/hooks/charmhelpers/contrib/openstack/amulet/utils.py +++ b/hooks/charmhelpers/contrib/openstack/amulet/utils.py @@ -92,7 +92,7 @@ class OpenStackAmuletUtils(AmuletUtils): return 'endpoint not found' def validate_v3_endpoint_data(self, endpoints, admin_port, internal_port, - public_port, expected): + public_port, expected, expected_num_eps=3): """Validate keystone v3 endpoint data. Validate the v3 endpoint data which has changed from v2. The @@ -138,7 +138,7 @@ class OpenStackAmuletUtils(AmuletUtils): if ret: return 'unexpected endpoint data - {}'.format(ret) - if len(found) != 3: + if len(found) != expected_num_eps: return 'Unexpected number of endpoints found' def validate_svc_catalog_endpoint_data(self, expected, actual): diff --git a/hooks/charmhelpers/contrib/openstack/context.py b/hooks/charmhelpers/contrib/openstack/context.py index 52d0cebf..36cf32fc 100644 --- a/hooks/charmhelpers/contrib/openstack/context.py +++ b/hooks/charmhelpers/contrib/openstack/context.py @@ -1637,18 +1637,84 @@ class InternalEndpointContext(OSContextGenerator): endpoints by default so this allows admins to optionally use internal endpoints. """ - def __init__(self, ost_rel_check_pkg_name): - self.ost_rel_check_pkg_name = ost_rel_check_pkg_name + def __call__(self): + return {'use_internal_endpoints': config('use-internal-endpoints')} + + +class VolumeAPIContext(InternalEndpointContext): + """Volume API context. + + This context provides information regarding the volume endpoint to use + when communicating between services. It determines which version of the + API is appropriate for use. + + This value will be determined in the resulting context dictionary + returned from calling the VolumeAPIContext object. Information provided + by this context is as follows: + + volume_api_version: the volume api version to use, currently + 'v2' or 'v3' + volume_catalog_info: the information to use for a cinder client + configuration that consumes API endpoints from the keystone + catalog. This is defined as the type:name:endpoint_type string. + """ + # FIXME(wolsen) This implementation is based on the provider being able + # to specify the package version to check but does not guarantee that the + # volume service api version selected is available. In practice, it is + # quite likely the volume service *is* providing the v3 volume service. + # This should be resolved when the service-discovery spec is implemented. + def __init__(self, pkg): + """ + Creates a new VolumeAPIContext for use in determining which version + of the Volume API should be used for communication. A package codename + should be supplied for determining the currently installed OpenStack + version. + + :param pkg: the package codename to use in order to determine the + component version (e.g. nova-common). See + charmhelpers.contrib.openstack.utils.PACKAGE_CODENAMES for more. + """ + super(VolumeAPIContext, self).__init__() + self._ctxt = None + if not pkg: + raise ValueError('package name must be provided in order to ' + 'determine current OpenStack version.') + self.pkg = pkg + + @property + def ctxt(self): + if self._ctxt is not None: + return self._ctxt + self._ctxt = self._determine_ctxt() + return self._ctxt + + def _determine_ctxt(self): + """Determines the Volume API endpoint information. + + Determines the appropriate version of the API that should be used + as well as the catalog_info string that would be supplied. Returns + a dict containing the volume_api_version and the volume_catalog_info. + """ + rel = os_release(self.pkg, base='icehouse') + version = '2' + if CompareOpenStackReleases(rel) >= 'pike': + version = '3' + + service_type = 'volumev{version}'.format(version=version) + service_name = 'cinderv{version}'.format(version=version) + endpoint_type = 'publicURL' + if config('use-internal-endpoints'): + endpoint_type = 'internalURL' + catalog_info = '{type}:{name}:{endpoint}'.format( + type=service_type, name=service_name, endpoint=endpoint_type) + + return { + 'volume_api_version': version, + 'volume_catalog_info': catalog_info, + } def __call__(self): - ctxt = {'use_internal_endpoints': config('use-internal-endpoints')} - rel = os_release(self.ost_rel_check_pkg_name, base='icehouse') - if CompareOpenStackReleases(rel) >= 'pike': - ctxt['volume_api_version'] = '3' - else: - ctxt['volume_api_version'] = '2' - - return ctxt + return self.ctxt class AppArmorContext(OSContextGenerator): diff --git a/hooks/charmhelpers/core/templating.py b/hooks/charmhelpers/core/templating.py index 7b801a34..9014015c 100644 --- a/hooks/charmhelpers/core/templating.py +++ b/hooks/charmhelpers/core/templating.py @@ -20,7 +20,8 @@ from charmhelpers.core import hookenv def render(source, target, context, owner='root', group='root', - perms=0o444, templates_dir=None, encoding='UTF-8', template_loader=None): + perms=0o444, templates_dir=None, encoding='UTF-8', + template_loader=None, config_template=None): """ Render a template. @@ -32,6 +33,9 @@ def render(source, target, context, owner='root', group='root', The context should be a dict containing the values to be replaced in the template. + config_template may be provided to render from a provided template instead + of loading from a file. + The `owner`, `group`, and `perms` options will be passed to `write_file`. If omitted, `templates_dir` defaults to the `templates` folder in the charm. @@ -65,14 +69,19 @@ def render(source, target, context, owner='root', group='root', if templates_dir is None: templates_dir = os.path.join(hookenv.charm_dir(), 'templates') template_env = Environment(loader=FileSystemLoader(templates_dir)) - try: - source = source - template = template_env.get_template(source) - except exceptions.TemplateNotFound as e: - hookenv.log('Could not load template %s from %s.' % - (source, templates_dir), - level=hookenv.ERROR) - raise e + + # load from a string if provided explicitly + if config_template is not None: + template = template_env.from_string(config_template) + else: + try: + source = source + template = template_env.get_template(source) + except exceptions.TemplateNotFound as e: + hookenv.log('Could not load template %s from %s.' % + (source, templates_dir), + level=hookenv.ERROR) + raise e content = template.render(context) if target is not None: target_dir = os.path.dirname(target) diff --git a/tests/charmhelpers/contrib/openstack/amulet/utils.py b/tests/charmhelpers/contrib/openstack/amulet/utils.py index 87f364d1..d93cff3c 100644 --- a/tests/charmhelpers/contrib/openstack/amulet/utils.py +++ b/tests/charmhelpers/contrib/openstack/amulet/utils.py @@ -92,7 +92,7 @@ class OpenStackAmuletUtils(AmuletUtils): return 'endpoint not found' def validate_v3_endpoint_data(self, endpoints, admin_port, internal_port, - public_port, expected): + public_port, expected, expected_num_eps=3): """Validate keystone v3 endpoint data. Validate the v3 endpoint data which has changed from v2. The @@ -138,7 +138,7 @@ class OpenStackAmuletUtils(AmuletUtils): if ret: return 'unexpected endpoint data - {}'.format(ret) - if len(found) != 3: + if len(found) != expected_num_eps: return 'Unexpected number of endpoints found' def validate_svc_catalog_endpoint_data(self, expected, actual): diff --git a/tests/charmhelpers/core/templating.py b/tests/charmhelpers/core/templating.py index 7b801a34..9014015c 100644 --- a/tests/charmhelpers/core/templating.py +++ b/tests/charmhelpers/core/templating.py @@ -20,7 +20,8 @@ from charmhelpers.core import hookenv def render(source, target, context, owner='root', group='root', - perms=0o444, templates_dir=None, encoding='UTF-8', template_loader=None): + perms=0o444, templates_dir=None, encoding='UTF-8', + template_loader=None, config_template=None): """ Render a template. @@ -32,6 +33,9 @@ def render(source, target, context, owner='root', group='root', The context should be a dict containing the values to be replaced in the template. + config_template may be provided to render from a provided template instead + of loading from a file. + The `owner`, `group`, and `perms` options will be passed to `write_file`. If omitted, `templates_dir` defaults to the `templates` folder in the charm. @@ -65,14 +69,19 @@ def render(source, target, context, owner='root', group='root', if templates_dir is None: templates_dir = os.path.join(hookenv.charm_dir(), 'templates') template_env = Environment(loader=FileSystemLoader(templates_dir)) - try: - source = source - template = template_env.get_template(source) - except exceptions.TemplateNotFound as e: - hookenv.log('Could not load template %s from %s.' % - (source, templates_dir), - level=hookenv.ERROR) - raise e + + # load from a string if provided explicitly + if config_template is not None: + template = template_env.from_string(config_template) + else: + try: + source = source + template = template_env.get_template(source) + except exceptions.TemplateNotFound as e: + hookenv.log('Could not load template %s from %s.' % + (source, templates_dir), + level=hookenv.ERROR) + raise e content = template.render(context) if target is not None: target_dir = os.path.dirname(target) diff --git a/tox.ini b/tox.ini index 6d44f4b9..dae53621 100644 --- a/tox.ini +++ b/tox.ini @@ -9,7 +9,7 @@ skipsdist = True setenv = VIRTUAL_ENV={envdir} PYTHONHASHSEED=0 CHARM_DIR={envdir} - AMULET_SETUP_TIMEOUT=2700 + AMULET_SETUP_TIMEOUT=5400 install_command = pip install --allow-unverified python-apt {opts} {packages} commands = ostestr {posargs}