Allow bespoke get_charm_instance method to be used

Allow a charm to use the new register_get_charm_instance decorator
to register the method to be used when getting a charm instance.
Currently get_charm_instance expects standard OpenStack
versioning, this change allows are a charm to register an
alternative get_charm_instance method that can handle an
alternative versioning system.

Perhaps controversially the default method is not defined in
charms_openstack.charm.defaults. I have kept it in core
because it felt a more natural fit there given it relies on
core._releases but I am open to moving it if that is
preferable.

Change-Id: I42574654bc1f314b49049e80861d4039f8484dff
This commit is contained in:
Liam Young 2020-12-03 10:03:02 +00:00
parent 4907569ebc
commit 38de241ce6
2 changed files with 80 additions and 1 deletions

View File

@ -45,6 +45,10 @@ _singleton = None
# This is to enable the defining code to define which release is used. # This is to enable the defining code to define which release is used.
_release_selector_function = None _release_selector_function = None
# `_get_charm_instance_function` holds a function that takes optionally takes a
# release and returns the corresponding charm class.
_get_charm_instance_function = None
# `_package_type_selector_function` holds a function that optionally takes a # `_package_type_selector_function` holds a function that optionally takes a
# package type and commutes it to another package type or just returns a # package type and commutes it to another package type or just returns a
# package type. This is to enable the defining code to define which # package type. This is to enable the defining code to define which
@ -100,7 +104,8 @@ class provide_charm_instance(object):
return False return False
def get_charm_instance(release=None, package_type='deb', *args, **kwargs): def default_get_charm_instance(release=None, package_type='deb', *args,
**kwargs):
"""Get an instance of the charm based on the release (or use the """Get an instance of the charm based on the release (or use the
default if release is None). default if release is None).
@ -147,6 +152,24 @@ def get_charm_instance(release=None, package_type='deb', *args, **kwargs):
return cls(release=release, *args, **kwargs) return cls(release=release, *args, **kwargs)
def get_charm_instance(release=None, package_type='deb', *args, **kwargs):
"""Get an instance of the charm based on the release (or use the
default if release is None).
Use a bespoke method if one is registered otherwise uses the default
default_get_charm_instance.
:param release: lc string representing release wanted.
:param package_type: string representing the package type required
:returns: BaseOpenStackCharm() derived class according to cls.releases
"""
return (_get_charm_instance_function or default_get_charm_instance)(
release=release,
package_type=package_type,
*args,
**kwargs)
def register_os_release_selector(f): def register_os_release_selector(f):
"""Register a function that determines what the release is for the """Register a function that determines what the release is for the
invocation run. This allows the charm to define HOW the release is invocation run. This allows the charm to define HOW the release is
@ -171,6 +194,31 @@ def register_os_release_selector(f):
return f return f
def register_get_charm_instance(f):
"""Register a function that supplies a charm class for a given
release.
Usage:
@register_get_charm_instance
def my_get_charm_instance(release=None, *args, **kwargs):
if release == X:
cls = CharmClassX
return cls(release=release, *args, **kwargs)
The function should return a string which is an OS release.
"""
global _get_charm_instance_function
if _get_charm_instance_function is None:
# we can only do this once in a system invocation.
_get_charm_instance_function = f
else:
raise RuntimeError(
"Only a single get_charm_instance is supported."
" Called with {}".format(f.__name__))
return f
def register_package_type_selector(f): def register_package_type_selector(f):
"""Register a function that determines what the package type is for the """Register a function that determines what the package type is for the
invocation run. This allows the charm to define HOW the package type is invocation run. This allows the charm to define HOW the package type is

View File

@ -57,6 +57,36 @@ class TestRegisterOSReleaseSelector(unittest.TestCase):
chm_core._release_selector_function = save_rsf chm_core._release_selector_function = save_rsf
class TestRegisterGetCharmInstance(unittest.TestCase):
def test_register(self):
save_rsf = chm_core._get_charm_instance_function
chm_core._get_charm_instance_function = None
@chm_core.register_get_charm_instance
def test_func():
pass
self.assertEqual(chm_core._get_charm_instance_function, test_func)
chm_core._get_charm_instance_function = save_rsf
def test_cant_register_more_than_once(self):
save_rsf = chm_core._get_charm_instance_function
chm_core._get_charm_instance_function = None
@chm_core.register_get_charm_instance
def test_func1():
pass
with self.assertRaises(RuntimeError):
@chm_core.register_get_charm_instance
def test_func2():
pass
self.assertEqual(chm_core._get_charm_instance_function, test_func1)
chm_core._get_charm_instance_function = save_rsf
class TestBaseOpenStackCharmMeta(BaseOpenStackCharmTest): class TestBaseOpenStackCharmMeta(BaseOpenStackCharmTest):
def setUp(self): def setUp(self):
@ -103,6 +133,7 @@ class TestFunctions(BaseOpenStackCharmTest):
def setUp(self): def setUp(self):
super().setUp(chm_core.BaseOpenStackCharm, TEST_CONFIG) super().setUp(chm_core.BaseOpenStackCharm, TEST_CONFIG)
self.patch_object(chm_core, '_releases', new={}) self.patch_object(chm_core, '_releases', new={})
chm_core._get_charm_instance_function = None
class TestC1(chm_core.BaseOpenStackCharm): class TestC1(chm_core.BaseOpenStackCharm):
release = 'icehouse' release = 'icehouse'