From 335b67c66a835e983349ecd4579619c333a77b48 Mon Sep 17 00:00:00 2001 From: Liam Young Date: Mon, 17 Feb 2020 11:03:49 +0000 Subject: [PATCH] Watch & react to placement and neutron ep changes. Request to be informed of changes to placement and neutron changes. If a placement change occurs restart nova-scheduler as it will cache the old endpoint url and tell nova-compute to restart its services as they will have done the same. Change-Id: I7537723e40a5a25672fbbdc2d5c3144724f6240a Closes-Bug: #1862974 --- charmhelpers/contrib/openstack/policyd.py | 23 +++--- charmhelpers/contrib/openstack/utils.py | 87 ++++++++++++++++++++++ charmhelpers/contrib/storage/linux/ceph.py | 2 +- charmhelpers/core/host_factory/ubuntu.py | 1 + charmhelpers/osplatform.py | 24 +++++- hooks/nova_cc_hooks.py | 20 ++++- 6 files changed, 138 insertions(+), 19 deletions(-) diff --git a/charmhelpers/contrib/openstack/policyd.py b/charmhelpers/contrib/openstack/policyd.py index d89d2cca..f2bb21e9 100644 --- a/charmhelpers/contrib/openstack/policyd.py +++ b/charmhelpers/contrib/openstack/policyd.py @@ -17,7 +17,6 @@ import contextlib import os import six import shutil -import sys import yaml 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) if not os.path.exists(path): 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): # see if the path should be kept. if direntry.path in keep_paths: @@ -560,23 +559,25 @@ def maybe_create_directory_for(path, user, group): @contextlib.contextmanager -def _py2_scandir(path): - """provide a py2 implementation of os.scandir if this module ever gets used - 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 +def _fallback_scandir(path): + """Fallback os.scandir implementation. + + 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. :param path: the path to list the directories for :type path: str - :returns: Generator that provides _P27Direntry objects - :rtype: ContextManager[_P27Direntry] + :returns: Generator that provides _FBDirectory objects + :rtype: ContextManager[_FBDirectory] """ for f in os.listdir(path): - yield _P27Direntry(f) + yield _FBDirectory(f) -class _P27Direntry(object): - """Mock a scandir Direntry object with enough to use in +class _FBDirectory(object): + """Mock a scandir Directory object with enough to use in clean_policyd_dir_for """ diff --git a/charmhelpers/contrib/openstack/utils.py b/charmhelpers/contrib/openstack/utils.py index 161199c4..2b522eac 100644 --- a/charmhelpers/contrib/openstack/utils.py +++ b/charmhelpers/contrib/openstack/utils.py @@ -658,6 +658,93 @@ def config_value_changed(option): 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): """ Write an rc file in the charm-delivered directory containing diff --git a/charmhelpers/contrib/storage/linux/ceph.py b/charmhelpers/contrib/storage/linux/ceph.py index 104977af..dabfb6c2 100644 --- a/charmhelpers/contrib/storage/linux/ceph.py +++ b/charmhelpers/contrib/storage/linux/ceph.py @@ -1042,7 +1042,7 @@ def filesystem_mounted(fs): def make_filesystem(blk_device, fstype='ext4', timeout=10): """Make a new filesystem on the specified block device.""" count = 0 - e_noent = os.errno.ENOENT + e_noent = errno.ENOENT while not os.path.exists(blk_device): if count >= timeout: log('Gave up waiting on block device %s' % blk_device, diff --git a/charmhelpers/core/host_factory/ubuntu.py b/charmhelpers/core/host_factory/ubuntu.py index 1b57e2ce..3edc0687 100644 --- a/charmhelpers/core/host_factory/ubuntu.py +++ b/charmhelpers/core/host_factory/ubuntu.py @@ -25,6 +25,7 @@ UBUNTU_RELEASES = ( 'cosmic', 'disco', 'eoan', + 'focal' ) diff --git a/charmhelpers/osplatform.py b/charmhelpers/osplatform.py index c7fd1363..78c81af5 100644 --- a/charmhelpers/osplatform.py +++ b/charmhelpers/osplatform.py @@ -1,4 +1,5 @@ import platform +import os def get_platform(): @@ -9,9 +10,13 @@ def get_platform(): 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] + # Warnings *not* disabled, as we certainly need to fix this. + if hasattr(platform, 'linux_distribution'): + tuple_platform = platform.linux_distribution() + current_platform = tuple_platform[0] + else: + current_platform = _get_platform_from_fs() + if "Ubuntu" in current_platform: return "ubuntu" elif "CentOS" in current_platform: @@ -26,3 +31,16 @@ def get_platform(): else: raise RuntimeError("This module is not supported on {}." .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"] diff --git a/hooks/nova_cc_hooks.py b/hooks/nova_cc_hooks.py index ca801295..a1f1891e 100755 --- a/hooks/nova_cc_hooks.py +++ b/hooks/nova_cc_hooks.py @@ -434,10 +434,12 @@ def identity_joined(rid=None): public_url = ch_ip.canonical_url(CONFIGS, ch_ip.PUBLIC) internal_url = ch_ip.canonical_url(CONFIGS, ch_ip.INTERNAL) admin_url = ch_ip.canonical_url(CONFIGS, ch_ip.ADMIN) - hookenv.relation_set(relation_id=rid, - **ncc_utils.determine_endpoints(public_url, - internal_url, - admin_url)) + settings = ncc_utils.determine_endpoints( + public_url, + internal_url, + admin_url) + settings['subscribe_ep_change'] = 'neutron placement' + hookenv.relation_set(relation_id=rid, relation_settings=settings) @hooks.hook('identity-service-relation-changed') @@ -456,6 +458,16 @@ def identity_changed(): for rid in hookenv.relation_ids('neutron-api'): neutron_api_relation_joined(rid) configure_https() + if ch_utils.endpoint_changed('placement'): + hookenv.log("Placement endpoint has changed, restarting services", + "INFO") + ch_host.service_restart('nova-scheduler') + hookenv.log(("Placement endpoint has changed, requesting nova-compute " + "restart"), + "INFO") + for rid in hookenv.relation_ids('cloud-compute'): + compute_joined(rid=rid, remote_restart=True) + ch_utils.save_endpoint_changed_triggers(['placement']) @hooks.hook('nova-volume-service-relation-joined',