Sync charm-helpers for py38, distro, and other updates

Change-Id: I7964252f9d65b17915ac1d324b186955213a6297
This commit is contained in:
Ryan Beisner 2020-03-05 13:30:45 +01:00
parent ffb93815a0
commit f57bb43fe8
No known key found for this signature in database
GPG Key ID: 952BACDC1C1A05FB
6 changed files with 142 additions and 23 deletions
Makefile
charmhelpers
contrib
openstack
storage/linux
core/host_factory
osplatform.py

@ -1,5 +1,5 @@
#!/usr/bin/make #!/usr/bin/make
PYTHON := /usr/bin/env python PYTHON := /usr/bin/env python3
lint: lint:
@tox -e pep8 @tox -e pep8

@ -17,7 +17,6 @@ import contextlib
import os import os
import six import six
import shutil import shutil
import sys
import yaml import yaml
import zipfile import zipfile
@ -531,7 +530,7 @@ def clean_policyd_dir_for(service, keep_paths=None, user=None, group=None):
hookenv.log("Cleaning path: {}".format(path), level=hookenv.DEBUG) hookenv.log("Cleaning path: {}".format(path), level=hookenv.DEBUG)
if not os.path.exists(path): if not os.path.exists(path):
ch_host.mkdir(path, owner=_user, group=_group, perms=0o775) ch_host.mkdir(path, owner=_user, group=_group, perms=0o775)
_scanner = os.scandir if sys.version_info > (3, 4) else _py2_scandir _scanner = os.scandir if hasattr(os, 'scandir') else _fallback_scandir
for direntry in _scanner(path): for direntry in _scanner(path):
# see if the path should be kept. # see if the path should be kept.
if direntry.path in keep_paths: if direntry.path in keep_paths:
@ -560,23 +559,25 @@ def maybe_create_directory_for(path, user, group):
@contextlib.contextmanager @contextlib.contextmanager
def _py2_scandir(path): def _fallback_scandir(path):
"""provide a py2 implementation of os.scandir if this module ever gets used """Fallback os.scandir implementation.
in a py2 charm (unlikely). uses os.listdir() to get the names in the path,
and then mocks the is_dir() function using os.path.isdir() to check for a provide a fallback implementation of os.scandir if this module ever gets
used in a py2 or py34 charm. Uses os.listdir() to get the names in the path,
and then mocks the is_dir() function using os.path.isdir() to check for
directory. directory.
:param path: the path to list the directories for :param path: the path to list the directories for
:type path: str :type path: str
:returns: Generator that provides _P27Direntry objects :returns: Generator that provides _FBDirectory objects
:rtype: ContextManager[_P27Direntry] :rtype: ContextManager[_FBDirectory]
""" """
for f in os.listdir(path): for f in os.listdir(path):
yield _P27Direntry(f) yield _FBDirectory(f)
class _P27Direntry(object): class _FBDirectory(object):
"""Mock a scandir Direntry object with enough to use in """Mock a scandir Directory object with enough to use in
clean_policyd_dir_for clean_policyd_dir_for
""" """

@ -278,7 +278,7 @@ PACKAGE_CODENAMES = {
('14', 'rocky'), ('14', 'rocky'),
('15', 'stein'), ('15', 'stein'),
('16', 'train'), ('16', 'train'),
('17', 'ussuri'), ('18', 'ussuri'),
]), ]),
'ceilometer-common': OrderedDict([ 'ceilometer-common': OrderedDict([
('5', 'liberty'), ('5', 'liberty'),
@ -326,7 +326,7 @@ PACKAGE_CODENAMES = {
('14', 'rocky'), ('14', 'rocky'),
('15', 'stein'), ('15', 'stein'),
('16', 'train'), ('16', 'train'),
('17', 'ussuri'), ('18', 'ussuri'),
]), ]),
} }
@ -555,9 +555,8 @@ def reset_os_release():
_os_rel = None _os_rel = None
def os_release(package, base=None, reset_cache=False): def os_release(package, base=None, reset_cache=False, source_key=None):
''' """Returns OpenStack release codename from a cached global.
Returns OpenStack release codename from a cached global.
If reset_cache then unset the cached os_release version and return the If reset_cache then unset the cached os_release version and return the
freshly determined version. freshly determined version.
@ -565,7 +564,20 @@ def os_release(package, base=None, reset_cache=False):
If the codename can not be determined from either an installed package or If the codename can not be determined from either an installed package or
the installation source, the earliest release supported by the charm should the installation source, the earliest release supported by the charm should
be returned. be returned.
'''
:param package: Name of package to determine release from
:type package: str
:param base: Fallback codename if endavours to determine from package fail
:type base: Optional[str]
:param reset_cache: Reset any cached codename value
:type reset_cache: bool
:param source_key: Name of source configuration option
(default: 'openstack-origin')
:type source_key: Optional[str]
:returns: OpenStack release codename
:rtype: str
"""
source_key = source_key or 'openstack-origin'
if not base: if not base:
base = UBUNTU_OPENSTACK_RELEASE[lsb_release()['DISTRIB_CODENAME']] base = UBUNTU_OPENSTACK_RELEASE[lsb_release()['DISTRIB_CODENAME']]
global _os_rel global _os_rel
@ -575,7 +587,7 @@ def os_release(package, base=None, reset_cache=False):
return _os_rel return _os_rel
_os_rel = ( _os_rel = (
get_os_codename_package(package, fatal=False) or get_os_codename_package(package, fatal=False) or
get_os_codename_install_source(config('openstack-origin')) or get_os_codename_install_source(config(source_key)) or
base) base)
return _os_rel return _os_rel
@ -658,6 +670,93 @@ def config_value_changed(option):
return current != saved return current != saved
def get_endpoint_key(service_name, relation_id, unit_name):
"""Return the key used to refer to an ep changed notification from a unit.
:param service_name: Service name eg nova, neutron, placement etc
:type service_name: str
:param relation_id: The id of the relation the unit is on.
:type relation_id: str
:param unit_name: The name of the unit publishing the notification.
:type unit_name: str
:returns: The key used to refer to an ep changed notification from a unit
:rtype: str
"""
return '{}-{}-{}'.format(
service_name,
relation_id.replace(':', '_'),
unit_name.replace('/', '_'))
def get_endpoint_notifications(service_names, rel_name='identity-service'):
"""Return all notifications for the given services.
:param service_names: List of service name.
:type service_name: List
:param rel_name: Name of the relation to query
:type rel_name: str
:returns: A dict containing the source of the notification and its nonce.
:rtype: Dict[str, str]
"""
notifications = {}
for rid in relation_ids(rel_name):
for unit in related_units(relid=rid):
ep_changed_json = relation_get(
rid=rid,
unit=unit,
attribute='ep_changed')
if ep_changed_json:
ep_changed = json.loads(ep_changed_json)
for service in service_names:
if ep_changed.get(service):
key = get_endpoint_key(service, rid, unit)
notifications[key] = ep_changed[service]
return notifications
def endpoint_changed(service_name, rel_name='identity-service'):
"""Whether a new notification has been recieved for an endpoint.
:param service_name: Service name eg nova, neutron, placement etc
:type service_name: str
:param rel_name: Name of the relation to query
:type rel_name: str
:returns: Whether endpoint has changed
:rtype: bool
"""
changed = False
with unitdata.HookData()() as t:
db = t[0]
notifications = get_endpoint_notifications(
[service_name],
rel_name=rel_name)
for key, nonce in notifications.items():
if db.get(key) != nonce:
juju_log(('New endpoint change notification found: '
'{}={}').format(key, nonce),
'INFO')
changed = True
break
return changed
def save_endpoint_changed_triggers(service_names, rel_name='identity-service'):
"""Save the enpoint triggers in db so it can be tracked if they changed.
:param service_names: List of service name.
:type service_name: List
:param rel_name: Name of the relation to query
:type rel_name: str
"""
with unitdata.HookData()() as t:
db = t[0]
notifications = get_endpoint_notifications(
service_names,
rel_name=rel_name)
for key, nonce in notifications.items():
db.set(key, nonce)
def save_script_rc(script_path="scripts/scriptrc", **env_vars): def save_script_rc(script_path="scripts/scriptrc", **env_vars):
""" """
Write an rc file in the charm-delivered directory containing Write an rc file in the charm-delivered directory containing

@ -1042,7 +1042,7 @@ def filesystem_mounted(fs):
def make_filesystem(blk_device, fstype='ext4', timeout=10): def make_filesystem(blk_device, fstype='ext4', timeout=10):
"""Make a new filesystem on the specified block device.""" """Make a new filesystem on the specified block device."""
count = 0 count = 0
e_noent = os.errno.ENOENT e_noent = errno.ENOENT
while not os.path.exists(blk_device): while not os.path.exists(blk_device):
if count >= timeout: if count >= timeout:
log('Gave up waiting on block device %s' % blk_device, log('Gave up waiting on block device %s' % blk_device,

@ -25,6 +25,7 @@ UBUNTU_RELEASES = (
'cosmic', 'cosmic',
'disco', 'disco',
'eoan', 'eoan',
'focal'
) )

@ -1,4 +1,5 @@
import platform import platform
import os
def get_platform(): def get_platform():
@ -9,9 +10,13 @@ def get_platform():
This string is used to decide which platform module should be imported. This string is used to decide which platform module should be imported.
""" """
# linux_distribution is deprecated and will be removed in Python 3.7 # linux_distribution is deprecated and will be removed in Python 3.7
# Warings *not* disabled, as we certainly need to fix this. # Warnings *not* disabled, as we certainly need to fix this.
tuple_platform = platform.linux_distribution() if hasattr(platform, 'linux_distribution'):
current_platform = tuple_platform[0] tuple_platform = platform.linux_distribution()
current_platform = tuple_platform[0]
else:
current_platform = _get_platform_from_fs()
if "Ubuntu" in current_platform: if "Ubuntu" in current_platform:
return "ubuntu" return "ubuntu"
elif "CentOS" in current_platform: elif "CentOS" in current_platform:
@ -26,3 +31,16 @@ def get_platform():
else: else:
raise RuntimeError("This module is not supported on {}." raise RuntimeError("This module is not supported on {}."
.format(current_platform)) .format(current_platform))
def _get_platform_from_fs():
"""Get Platform from /etc/os-release."""
with open(os.path.join(os.sep, 'etc', 'os-release')) as fin:
content = dict(
line.split('=', 1)
for line in fin.read().splitlines()
if '=' in line
)
for k, v in content.items():
content[k] = v.strip('"')
return content["NAME"]