From fba093189cb81454b897709caaeb2ae022029fbd Mon Sep 17 00:00:00 2001 From: Aurelien Lourot Date: Tue, 28 Sep 2021 14:19:08 +0200 Subject: [PATCH] Publish releases packages map to principal charm For principal - subordinate plugin type relations where the principal Python payload imports code from packages managed by a subordinate, upgrades can be problematic. This change will allow a subordinate charm that have opted into the feature to inform its principal about all implemented release - packages combinations ahead of time. With this information in place the principal can do the upgrade in one operation without risk of charm relation RPC type processing at a critical moment. This is similar to https://review.opendev.org/c/openstack/charm-interface-keystone-domain-backend/+/781658 https://review.opendev.org/c/openstack/charm-layer-openstack/+/781624 Change-Id: Ibd5bdcb141fc3103ee97123ff284fb2957802eba Closes-Bug: #1927277 (cherry picked from commit be45f7794504514ffbd4bc721af8f3df9a4c6038) --- hooks/ceilometer_hooks.py | 11 ++++-- hooks/ceilometer_utils.py | 54 +++++++++++++++++++++++------ test-requirements.txt | 4 +++ unit_tests/test_ceilometer_hooks.py | 22 ++++++++++-- unit_tests/test_ceilometer_utils.py | 12 +++++++ 5 files changed, 89 insertions(+), 14 deletions(-) diff --git a/hooks/ceilometer_hooks.py b/hooks/ceilometer_hooks.py index 072ac24..b5c238d 100755 --- a/hooks/ceilometer_hooks.py +++ b/hooks/ceilometer_hooks.py @@ -59,6 +59,7 @@ from ceilometer_utils import ( NOVA_SETTINGS, assess_status, get_packages, + releases_packages_map, pause_unit_helper, resume_unit_helper, remove_old_packages, @@ -83,8 +84,14 @@ def install(): @hooks.hook('nova-ceilometer-relation-joined') def nova_ceilometer_joined(relation_id=None): - relation_set(relation_id=relation_id, - subordinate_configuration=json.dumps(NOVA_SETTINGS)) + relation_set( + relation_id=relation_id, + relation_settings={ + 'subordinate_configuration': json.dumps(NOVA_SETTINGS), + 'releases-packages-map': json.dumps( + releases_packages_map(), sort_keys=True), + 'services': json.dumps(services()) + }) @hooks.hook("ceilometer-service-relation-changed") diff --git a/hooks/ceilometer_utils.py b/hooks/ceilometer_utils.py index 93b8435..53fbcbe 100644 --- a/hooks/ceilometer_utils.py +++ b/hooks/ceilometer_utils.py @@ -115,8 +115,7 @@ def register_configs(): # if called without anything installed (eg during install hook) # just default to earliest supported release. configs dont get touched # till post-install, anyway. - release = get_os_codename_package('ceilometer-common', fatal=False) \ - or 'icehouse' + release = _get_current_release() configs = templating.OSConfigRenderer(templates_dir=TEMPLATES, openstack_release=release) @@ -137,8 +136,7 @@ def register_configs(): def get_packages(): - release = CompareOpenStackReleases(get_os_codename_package( - 'ceilometer-common', fatal=False) or 'icehouse') + release = _get_current_release() packages = deepcopy(CEILOMETER_AGENT_PACKAGES) packages.extend(token_cache_pkgs(release=release)) @@ -157,8 +155,7 @@ def determine_purge_packages(): :returns: list of package names ''' - release = CompareOpenStackReleases(get_os_codename_package( - 'ceilometer-common', fatal=False) or 'icehouse') + release = _get_current_release() if release >= 'rocky': pkgs = [p for p in CEILOMETER_AGENT_PACKAGES if p.startswith('python-')] @@ -166,6 +163,40 @@ def determine_purge_packages(): return [] +def releases_packages_map(): + '''Provide a map of all supported releases and their packages. + + NOTE(lourot): this is a simplified version of a more generic + implementation: + https://github.com/openstack/charms.openstack/blob/master/charms_openstack/charm/core.py + + :returns: Map of release, package type and install / purge packages. + Example: + { + 'mitaka': { + 'deb': { + 'install': ['python-ldappool'], + 'purge': [] + } + }, + 'rocky': { + 'deb': { + 'install': ['python3-ldap', 'python3-ldappool'], + 'purge': ['python-ldap', 'python-ldappool']} + } + } + :rtype: Dict[str,Dict[str,List[str]]] + ''' + return { + _get_current_release(): { + 'deb': { + 'install': get_packages(), + 'purge': determine_purge_packages(), + } + } + } + + def remove_old_packages(): '''Purge any packages that need ot be removed. @@ -183,8 +214,7 @@ def remove_old_packages(): def determine_held_packages(): '''Return a list of packages to mark as candidates for removal for the current OS release''' - release = CompareOpenStackReleases(get_os_codename_package( - 'ceilometer-common', fatal=False) or 'icehouse') + release = _get_current_release() if release >= 'rocky': return HELD_PACKAGES return [] @@ -198,8 +228,7 @@ def restart_map(): :returns: dict: A dictionary mapping config file to lists of services that should be restarted when file changes. ''' - release = (get_os_codename_package('ceilometer-common', fatal=False) or - 'icehouse') + release = _get_current_release() if CompareOpenStackReleases(release) >= 'queens': _config_files = QUEENS_CONFIG_FILES @@ -291,3 +320,8 @@ def _pause_resume_helper(f, configs): f(assess_status_func(configs), services=services(), ports=None) + + +def _get_current_release(): + return (get_os_codename_package('ceilometer-common', fatal=False) + or 'icehouse') diff --git a/test-requirements.txt b/test-requirements.txt index 89f4ce0..4032e2e 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -36,6 +36,10 @@ stevedore<1.31.0;python_version<'3.6' debtcollector<1.22.0;python_version<'3.6' oslo.utils<=3.41.0;python_version<'3.6' +# New openstacksdk versions depend on platformdirs>=3 which does not support +# python 3.6 +openstacksdk<1.6.0; python_version <= '3.6' + coverage>=4.5.2 pyudev # for ceph-* charm unit tests (need to fix the ceph-* charm unit tests/mocking) git+https://github.com/openstack-charmers/zaza.git@stable/ussuri#egg=zaza diff --git a/unit_tests/test_ceilometer_hooks.py b/unit_tests/test_ceilometer_hooks.py index 962f686..03e3df4 100644 --- a/unit_tests/test_ceilometer_hooks.py +++ b/unit_tests/test_ceilometer_hooks.py @@ -29,6 +29,8 @@ TO_PATCH = [ 'apt_update', 'filter_installed_packages', 'get_packages', + 'releases_packages_map', + 'services', 'is_relation_made', 'is_unit_paused_set', 'relation_set', @@ -65,11 +67,27 @@ class CeilometerHooksTest(CharmTestCase): @patch('charmhelpers.core.hookenv.config') def test_nova_ceilometer_joined(self, mock_config): + mocked_releases_packages_map = { + 'ussuri': { + 'deb': { + 'install': [ + 'ceilometer-common', 'ceilometer-agent-compute', + 'python3-ceilometer', 'python3-memcache'], + 'purge': ['python-ceilometer'], + }}} + mocked_services = ['ceilometer-agent-compute'] + + self.releases_packages_map.return_value = mocked_releases_packages_map + self.services.return_value = mocked_services hooks.hooks.execute(['hooks/nova-ceilometer-relation-joined']) self.relation_set.assert_called_with( relation_id=None, - subordinate_configuration=json.dumps( - ceilometer_utils.NOVA_SETTINGS)) + relation_settings={ + 'subordinate_configuration': json.dumps( + ceilometer_utils.NOVA_SETTINGS), + 'releases-packages-map': json.dumps( + mocked_releases_packages_map, sort_keys=True), + 'services': json.dumps(mocked_services)}) @patch('charmhelpers.core.hookenv.config') def test_config_changed(self, mock_config): diff --git a/unit_tests/test_ceilometer_utils.py b/unit_tests/test_ceilometer_utils.py index 20dd18d..467086e 100644 --- a/unit_tests/test_ceilometer_utils.py +++ b/unit_tests/test_ceilometer_utils.py @@ -157,3 +157,15 @@ class CeilometerUtilsTest(CharmTestCase): sorted([p for p in utils.CEILOMETER_AGENT_PACKAGES if not p.startswith('python-')] + ['python3-ceilometer', 'python3-memcache'])) + + def test_releases_packages_map(self): + self.get_os_codename_package.return_value = 'ussuri' + self.token_cache_pkgs.return_value = [] + self.assertEqual(utils.releases_packages_map(), { + 'ussuri': { + 'deb': { + 'install': [ + 'ceilometer-common', 'ceilometer-agent-compute', + 'python3-ceilometer', 'python3-memcache'], + 'purge': ['python-ceilometer'], + }}})