From dda431eaf92c3acae4f279a2ed0bf074a394f697 Mon Sep 17 00:00:00 2001 From: Frode Nordahl Date: Fri, 25 Feb 2022 11:22:53 +0100 Subject: [PATCH] core: Move package version helpers to BaseOpenStackCharmActions To resolve a inter-dependency issue between the various package version helpers and the UCA ``configure_source`` method, co-locate all of them in ``BaseOpenStackCharmActions``. Partial-Bug: #1951462 Change-Id: If42ad980ff2b0430eba24531eae9a80204768388 --- charms_openstack/charm/core.py | 359 +++++++++--------- .../charms_openstack/charm/test_core.py | 12 +- 2 files changed, 185 insertions(+), 186 deletions(-) diff --git a/charms_openstack/charm/core.py b/charms_openstack/charm/core.py index 77a9733..8eae603 100644 --- a/charms_openstack/charm/core.py +++ b/charms_openstack/charm/core.py @@ -533,186 +533,6 @@ class BaseOpenStackCharm(object, metaclass=BaseOpenStackCharmMeta): """proxy for charms.reactive.bus.get_state()""" return reactive.bus.get_state(state) - @staticmethod - def get_os_codename_snap(snap, codenames, fatal=True): - """Derive OpenStack release codename from an installed snap. - - :param package: str Snap name to lookup (ie. in snap list) - :param codenames: dict of OrderedDict - { - 'snap1': collections.OrderedDict([ - ('2', 'mitaka'), - ('3', 'newton'), - ('4', 'ocata'), ]), - 'snap2': collections.OrderedDict([ - ('12', 'mitaka'), - ('13', 'newton'), - ('14', 'ocata'), ]), - } - :param fatal: bool Raise exception if snap not installed - :returns: str OpenStack version name corresponding to package - """ - version_or_codename = get_snap_version(snap, fatal) - - match = re.match(r'^(\d+)\.(\d+)', version_or_codename) - if match: - version = match.group(0) - # Generate a major version number for newer semantic - # versions of openstack projects - major_vers = version.split('.')[0] - try: - return codenames[snap][major_vers] - except KeyError: - # NOTE(jamespage): fallthrough to codename assumption - pass - - # NOTE(jamespage): fallback to codename assumption - return version_or_codename - - @staticmethod - def get_package_version(package, apt_cache_sufficient=False): - """Derive OpenStack release codename from a package. - - :param package: Package name to lookup (ie. in apt cache) - :type package: str - :param apt_cache_sufficient: When False (the default) version from an - installed package will be used, when True version from the systems - APT cache will be used. This is useful for subordinate charms who - need working release selection prior to package installation and - has no way of using fall back to version of a package the principle - charm has installed nor package source configuration option. - :type apt_cache_sufficient: bool - :returns: OpenStack version name corresponding to package - :rtype: Optional[str] - :raises: AttributeError, ValueError - """ - cache = fetch.apt_cache() - - try: - pkg = cache[package] - except KeyError: - # the package is unknown to the current apt cache. - e = ValueError( - 'Could not determine version of package with no installation ' - 'candidate: {}'.format(package)) - raise e - - if apt_cache_sufficient: - vers = fetch.apt_pkg.upstream_version(pkg.version) - else: - vers = fetch.apt_pkg.upstream_version(pkg.current_ver.ver_str) - - # x.y match only for 20XX.X - # and ignore patch level for other packages - match = re.match(r'^(\d+)\.(\d+)', vers) - - if match: - vers = match.group(0) - - return vers - - @staticmethod - def get_os_codename_package(package, codenames, fatal=True, - apt_cache_sufficient=False): - """Derive OpenStack release codename from a package. - - - Initially, see if the openstack-release pkg is available (by trying - to install it) and use it instead. - - If it isn't then it falls back to the existing method of checking the - version of the package passed and then resolving the version from that - using lookup tables. - - :param package: Package name to lookup (ie. in apt cache) - :type package: str - :param codenames: Map of package to (version, os_release) tuples. - Example: - { - 'pkg1': collections.OrderedDict([ - ('2', 'mitaka'), - ('3', 'newton'), - ('4', 'ocata'), ]), - 'pkg2': collections.OrderedDict([ - ('12.6', 'mitaka'), - ('13.2', 'newton'), - ('14.7', 'ocata'), ]), - } - :type codenames: Dict[str,collections.OrderedDict[Tuple(str,str)]] - :param fatal: Raise exception if pkg not installed - :type fatal: bool - :param apt_cache_sufficient: When False (the default) version from an - installed package will be used, when True version from the systems - APT cache will be used. This is useful for subordinate charms who - need working release selection prior to package installation and - has no way of using fall back to version of a package the principle - charm has installed nor package source configuration option. - :type apt_cache_sufficient: bool - :returns: OpenStack version name corresponding to package - :rtype: Optional[str] - :raises: AttributeError, ValueError - """ - - codename = os_utils.get_installed_os_version() - if codename: - return codename - - try: - vers = BaseOpenStackCharm.get_package_version( - package, - apt_cache_sufficient=apt_cache_sufficient) - # Generate a major version number for newer semantic - # versions of openstack projects - major_vers = vers.split('.')[0] - except Exception: - if fatal: - raise - else: - return None - if (package in codenames and - major_vers in codenames[package]): - return codenames[package][major_vers] - - def get_os_version_snap(self, snap, fatal=True): - """Derive OpenStack version number from an installed snap. - - :param package: str Snap name to lookup in snap list - :param fatal: bool Raise exception if snap not installed - :returns: str OpenStack version number corresponding to snap - """ - if os_utils.snap_install_requested(): - codename = self.get_os_codename_snap(snap, - self.snap_codenames, - fatal=fatal) - if not codename: - return None - - for version, cname in os_utils.OPENSTACK_CODENAMES.items(): - if cname == codename: - return version - - return None - - def get_os_version_package(self, package, fatal=True): - """Derive OpenStack version number from an installed package. - - :param package: str Package name to lookup in apt cache - :param fatal: bool Raise exception if pkg not installed - :returns: str OpenStack version number corresponding to package - """ - if not os_utils.snap_install_requested(): - codename = self.get_os_codename_package( - package, self.package_codenames or os_utils.PACKAGE_CODENAMES, - fatal=fatal) - if not codename: - return None - - for version, cname in os_utils.OPENSTACK_CODENAMES.items(): - if cname == codename: - return version - - return None - class BaseOpenStackCharmActions(object): """Default actions that an OpenStack charm can expect to have to do. @@ -1265,6 +1085,185 @@ class BaseOpenStackCharmActions(object): """ return self.config[self.source_config_key].startswith('snap:') + @staticmethod + def get_os_codename_snap(snap, codenames, fatal=True): + """Derive OpenStack release codename from an installed snap. + + :param package: str Snap name to lookup (ie. in snap list) + :param codenames: dict of OrderedDict + { + 'snap1': collections.OrderedDict([ + ('2', 'mitaka'), + ('3', 'newton'), + ('4', 'ocata'), ]), + 'snap2': collections.OrderedDict([ + ('12', 'mitaka'), + ('13', 'newton'), + ('14', 'ocata'), ]), + } + :param fatal: bool Raise exception if snap not installed + :returns: str OpenStack version name corresponding to package + """ + version_or_codename = get_snap_version(snap, fatal) + + match = re.match(r'^(\d+)\.(\d+)', version_or_codename) + if match: + version = match.group(0) + # Generate a major version number for newer semantic + # versions of openstack projects + major_vers = version.split('.')[0] + try: + return codenames[snap][major_vers] + except KeyError: + # NOTE(jamespage): fallthrough to codename assumption + pass + + # NOTE(jamespage): fallback to codename assumption + return version_or_codename + + @staticmethod + def get_package_version(package, apt_cache_sufficient=False): + """Derive OpenStack release codename from a package. + + :param package: Package name to lookup (ie. in apt cache) + :type package: str + :param apt_cache_sufficient: When False (the default) version from an + installed package will be used, when True version from the systems + APT cache will be used. This is useful for subordinate charms who + need working release selection prior to package installation and + has no way of using fall back to version of a package the principle + charm has installed nor package source configuration option. + :type apt_cache_sufficient: bool + :returns: OpenStack version name corresponding to package + :rtype: Optional[str] + :raises: AttributeError, ValueError + """ + cache = fetch.apt_cache() + + try: + pkg = cache[package] + except KeyError: + # the package is unknown to the current apt cache. + e = ValueError( + 'Could not determine version of package with no installation ' + 'candidate: {}'.format(package)) + raise e + + if apt_cache_sufficient: + vers = fetch.apt_pkg.upstream_version(pkg.version) + else: + vers = fetch.apt_pkg.upstream_version(pkg.current_ver.ver_str) + + # x.y match only for 20XX.X + # and ignore patch level for other packages + match = re.match(r'^(\d+)\.(\d+)', vers) + + if match: + vers = match.group(0) + + return vers + + def get_os_codename_package(self, package, codenames, fatal=True, + apt_cache_sufficient=False): + """Derive OpenStack release codename from a package. + + + Initially, see if the openstack-release pkg is available (by trying + to install it) and use it instead. + + If it isn't then it falls back to the existing method of checking the + version of the package passed and then resolving the version from that + using lookup tables. + + :param package: Package name to lookup (ie. in apt cache) + :type package: str + :param codenames: Map of package to (version, os_release) tuples. + Example: + { + 'pkg1': collections.OrderedDict([ + ('2', 'mitaka'), + ('3', 'newton'), + ('4', 'ocata'), ]), + 'pkg2': collections.OrderedDict([ + ('12.6', 'mitaka'), + ('13.2', 'newton'), + ('14.7', 'ocata'), ]), + } + :type codenames: Dict[str,collections.OrderedDict[Tuple(str,str)]] + :param fatal: Raise exception if pkg not installed + :type fatal: bool + :param apt_cache_sufficient: When False (the default) version from an + installed package will be used, when True version from the systems + APT cache will be used. This is useful for subordinate charms who + need working release selection prior to package installation and + has no way of using fall back to version of a package the principle + charm has installed nor package source configuration option. + :type apt_cache_sufficient: bool + :returns: OpenStack version name corresponding to package + :rtype: Optional[str] + :raises: AttributeError, ValueError + """ + + codename = os_utils.get_installed_os_version() + if codename: + return codename + + try: + vers = self.get_package_version( + package, + apt_cache_sufficient=apt_cache_sufficient) + # Generate a major version number for newer semantic + # versions of openstack projects + major_vers = vers.split('.')[0] + except Exception: + if fatal: + raise + else: + return None + if (package in codenames and + major_vers in codenames[package]): + return codenames[package][major_vers] + + def get_os_version_snap(self, snap, fatal=True): + """Derive OpenStack version number from an installed snap. + + :param package: str Snap name to lookup in snap list + :param fatal: bool Raise exception if snap not installed + :returns: str OpenStack version number corresponding to snap + """ + if os_utils.snap_install_requested(): + codename = self.get_os_codename_snap(snap, + self.snap_codenames, + fatal=fatal) + if not codename: + return None + + for version, cname in os_utils.OPENSTACK_CODENAMES.items(): + if cname == codename: + return version + + return None + + def get_os_version_package(self, package, fatal=True): + """Derive OpenStack version number from an installed package. + + :param package: str Package name to lookup in apt cache + :param fatal: bool Raise exception if pkg not installed + :returns: str OpenStack version number corresponding to package + """ + if not os_utils.snap_install_requested(): + codename = self.get_os_codename_package( + package, self.package_codenames or os_utils.PACKAGE_CODENAMES, + fatal=fatal) + if not codename: + return None + + for version, cname in os_utils.OPENSTACK_CODENAMES.items(): + if cname == codename: + return version + + return None + class BaseOpenStackCharmAssessStatus(object): """Provides the 'Assess Status' functionality to the OpenStack charm class. diff --git a/unit_tests/charms_openstack/charm/test_core.py b/unit_tests/charms_openstack/charm/test_core.py index fc6b447..d5e394d 100644 --- a/unit_tests/charms_openstack/charm/test_core.py +++ b/unit_tests/charms_openstack/charm/test_core.py @@ -903,14 +903,14 @@ class TestMyOpenStackCharm(BaseOpenStackCharmTest): self.get_installed_os_version.return_value = None self.upstream_version.return_value = '3.0.0~b1' self.assertEqual( - chm_core.BaseOpenStackCharm.get_os_codename_package( + self.target.get_os_codename_package( 'testpkg', codenames), 'newton') self.upstream_version.assert_called_once_with( pkg_mock.current_ver.ver_str) self.upstream_version.reset_mock() self.assertEqual( - chm_core.BaseOpenStackCharm.get_os_codename_package( + self.target.get_os_codename_package( 'testpkg', codenames, apt_cache_sufficient=True), 'newton') self.upstream_version.assert_called_once_with( @@ -918,21 +918,21 @@ class TestMyOpenStackCharm(BaseOpenStackCharmTest): # Test Wallaby self.get_installed_os_version.return_value = 'wallaby' self.assertEqual( - chm_core.BaseOpenStackCharm.get_os_codename_package( + self.target.get_os_codename_package( 'testpkg', codenames), 'wallaby') # Test non-fatal fail self.get_installed_os_version.return_value = None self.assertEqual( - chm_core.BaseOpenStackCharm.get_os_codename_package( + self.target.get_os_codename_package( 'unknownpkg', codenames, fatal=False), None) # Test fatal fail with self.assertRaises(Exception): - chm_core.BaseOpenStackCharm.get_os_codename_package( + self.target.get_os_codename_package( 'unknownpkg', codenames, fatal=True) with self.assertRaises(ValueError): - chm_core.BaseOpenStackCharm.get_os_codename_package( + self.target.get_os_codename_package( 'unknownpkg', codenames, fatal=True) def test_get_os_version_package(self):