Allow release selection to work for subordinate charms

The release selection function bases its decision on installed
packages and falls back to looking up OpenStack release codename
from source configuration option when no packages are installed.

Subordinate charms do not have a source configuration option,
thus we want to make it possible for a charm author to request
the release selection decision be made based on what is currently
available in the system APT cache alone.

Our principle charm should already have configured the system
with the desired UCA pocket enabled.

The charm author requests this behaviour by setting the
``source_config_key`` to an empty string.

Note that functions for upgrading between releases etc also
depend on the charm having a source configuration option but
that is OK as the subordinate has no business dealing with
that and should defer such operations to its principle charm.

Change-Id: Ieae81619e06dc287c2c42ec944425be4de13d0d6
Closes-Bug: #1865510
This commit is contained in:
Frode Nordahl 2020-03-19 07:56:44 +01:00
parent 72cfde8308
commit d8b376cde2
No known key found for this signature in database
GPG Key ID: 6A5D59A3BA48373F
5 changed files with 44 additions and 7 deletions

View File

@ -125,6 +125,9 @@ class OpenStackCharm(BaseOpenStackCharm,
# Useful for charms managing software from UCA and consuming the
# `openstack` layer directly for re-use of common code, but not being a
# OpenStack component.
#
# Subordinate charms should set this to an empty string ('') to activate
# special handling of release selection.
source_config_key = 'openstack-origin'
@property

View File

@ -472,8 +472,9 @@ class BaseOpenStackCharm(object, metaclass=BaseOpenStackCharmMeta):
return version_or_codename
@staticmethod
def get_os_codename_package(package, codenames, fatal=True):
"""Derive OpenStack release codename from an installed package.
def get_os_codename_package(package, codenames, fatal=True,
apt_cache_sufficient=False):
"""Derive OpenStack release codename from a package.
:param package: Package name to lookup (ie. in apt cache)
:type package: str
@ -492,6 +493,13 @@ class BaseOpenStackCharm(object, metaclass=BaseOpenStackCharmMeta):
: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
@ -508,11 +516,15 @@ class BaseOpenStackCharm(object, metaclass=BaseOpenStackCharmMeta):
'Could not determine version of package with no installation '
'candidate: {}'.format(package))
raise e
if not pkg.current_ver:
if not fatal:
return None
vers = fetch.apt_pkg.upstream_version(pkg.current_ver.ver_str)
if apt_cache_sufficient:
vers = fetch.apt_pkg.upstream_version(pkg.version)
else:
if not pkg.current_ver:
if not fatal:
return None
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('^(\d+)\.(\d+)', vers)

View File

@ -103,7 +103,8 @@ def make_default_select_release_handler():
# instance defined package codename dictionary.
singleton = get_charm_instance()
release_version = singleton.get_os_codename_package(
singleton.release_pkg, singleton.package_codenames)
singleton.release_pkg, singleton.package_codenames,
apt_cache_sufficient=(not singleton.source_config_key))
if release_version is None:
# Surprisingly get_os_codename_package called with
# ``Fatal=True`` does not raise an error when the charm

View File

@ -787,6 +787,15 @@ class TestMyOpenStackCharm(BaseOpenStackCharmTest):
chm_core.BaseOpenStackCharm.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(
'testpkg', codenames, apt_cache_sufficient=True),
'newton')
self.upstream_version.assert_called_once_with(
pkg_mock.version)
# Test non-fatal fail
self.assertEqual(
chm_core.BaseOpenStackCharm.get_os_codename_package(

View File

@ -130,11 +130,23 @@ class TestDefaults(BaseOpenStackCharmTest):
'cinder-common', source_key='fake-config-key')
# No release set, charm class provides package_codenames
kv.reset_mock()
singleton.get_os_codename_package.reset_mock()
singleton.get_os_codename_package.side_effect = None
singleton.get_os_codename_package.return_value = 'three'
singleton.source_config_key = 'fake-config-key'
release = h.map['function']()
self.assertEqual(release, 'three')
singleton.get_os_codename_package.assert_called_once_with(
mock.ANY, mock.ANY, apt_cache_sufficient=False)
kv.set.assert_called_once_with(chm.OPENSTACK_RELEASE_KEY, 'three')
# No release set, charm class has empty ``source_config_key``
singleton.get_os_codename_package.reset_mock()
singleton.source_config_key = ''
singleton.get_os_codename_package.return_value = 'four'
release = h.map['function']()
self.assertEqual(release, 'four')
singleton.get_os_codename_package.assert_called_once_with(
mock.ANY, mock.ANY, apt_cache_sufficient=True)
def test_default_select_package_type_handler(self):
self.assertIn('charm.default-select-package-type',