commit af1dd4cdeef8432b854054b37e26d17a95c3ea17 Author: Frode Nordahl Date: Mon Sep 21 12:38:30 2020 +0200 plugins/ceph: Add common pool creation method The new helper supports to optionally retrieve common configuration options for BlueStore Compression. Fix ordering of Ceph class composition. Both the CephCharm and the fake charm class used in the unit test had the wrong order for the composition making it impossible to override already existing methods inherited from OpenStackCharm. Change-Id: I9fb543f297522c7217acdcfa950bb2810b26c4a0 diff --git a/charms_openstack/plugins/classes.py b/charms_openstack/plugins/classes.py index e31a95d..fc983b6 100644 --- a/charms_openstack/plugins/classes.py +++ b/charms_openstack/plugins/classes.py @@ -23,6 +23,7 @@ import charms_openstack.charm from charms_openstack.charm.classes import SNAP_PATH_PREFIX_FORMAT import charmhelpers.core as ch_core +import charmhelpers.contrib.openstack.context as ch_context import charmhelpers.contrib.openstack.policyd as ch_policyd @@ -216,9 +217,78 @@ class BaseOpenStackCephCharm(object): except OSError: return '' + @staticmethod + def _get_bluestore_compression(): + """Get BlueStore Compression charm configuration if present. -class CephCharm(charms_openstack.charm.OpenStackCharm, - BaseOpenStackCephCharm): + :returns: Dictionary of options suitable for passing on as keyword + arguments or None. + :rtype: Optional[Dict[str,any]] + :raises: ValueError + """ + try: + bluestore_compression = ( + ch_context.CephBlueStoreCompressionContext()) + bluestore_compression.validate() + except KeyError: + # The charm does not have BlueStore Compression options defined + bluestore_compression = None + if bluestore_compression: + return bluestore_compression.get_kwargs() + + def states_to_check(self, required_relations=None): + """Augment states to check handling. + + Validates Ceph specific configuration options and adds end user + feedback through juju status. + + :param required_relations: List of relations which overrides + self.relations + :type required_relations: Optional[List[str]] + :returns: Map of relations and their states to check + :rtype: Dict[str,List[Tuple[str,str,str]]] + """ + states_to_check = super().states_to_check( + required_relations=required_relations) + try: + self._get_bluestore_compression() + except ValueError as e: + # we add a made up relation to have the library set the status for + # us. + states_to_check['charm.bluestore_compression'] = [ + ('charm.bluestore_compression', + 'blocked', + 'Invalid configuration: {}'.format(str(e))), + ] + return states_to_check + + def create_pool(self, ceph_interface): + """Request pool for service. + + :param ceph_interface: Ceph interface instance + :type ceph_interface: CephRequires + """ + try: + bluestore_compression = self._get_bluestore_compression() + except ValueError as e: + # One or more of the values provided for the configuration options + # is invalid, do not attempt to create pool. The end user will be + # informed about the condition through juju status + # (see the ``states_to_check`` method above). + ch_core.hookenv.log('Invalid value(s) provided for Ceph BlueStore ' + 'compression: "{}"' + .format(str(e))) + return + kwargs = { + 'name': self.name, + } + if bluestore_compression: + kwargs.update(bluestore_compression) + ceph_interface.create_replicated_pool(**kwargs) + + +class CephCharm(BaseOpenStackCephCharm, + charms_openstack.charm.OpenStackCharm): """Class for charms deploying Ceph services. It provides useful defaults to make release detection work when no diff --git a/unit_tests/charms_openstack/plugins/test_classes.py b/unit_tests/charms_openstack/plugins/test_classes.py index c1eb54f..1bde0a4 100644 --- a/unit_tests/charms_openstack/plugins/test_classes.py +++ b/unit_tests/charms_openstack/plugins/test_classes.py @@ -13,8 +13,8 @@ TEST_CONFIG = {'config': True, class FakeOpenStackCephConsumingCharm( - chm.OpenStackCharm, - cpl.BaseOpenStackCephCharm): + cpl.BaseOpenStackCephCharm, + chm.OpenStackCharm): abstract_class = True @@ -134,6 +134,44 @@ class TestOpenStackCephConsumingCharm(BaseOpenStackCharmTest): self.remove.side_effect = OSError self.assertEqual(self.target.delete_ceph_keyring(), '') + def test__get_bluestore_compression(self): + self.patch_object(cpl.ch_context, 'CephBlueStoreCompressionContext') + bluestore_compression = mock.MagicMock() + expect = {'fake': 'value'} + bluestore_compression.get_kwargs.return_value = expect + self.CephBlueStoreCompressionContext.return_value = ( + bluestore_compression) + bluestore_compression.validate.side_effect = KeyError + self.assertEquals(self.target._get_bluestore_compression(), None) + bluestore_compression.validate.side_effect = None + self.assertDictEqual( + self.target._get_bluestore_compression(), + expect) + + def test_states_to_check(self): + self.patch_object(chm.OpenStackCharm, 'states_to_check', + name='parent_states_to_check') + expect = {'fake': [('state', 'message')]} + self.parent_states_to_check.return_value = expect + self.patch_target('_get_bluestore_compression') + self.assertDictEqual(self.target.states_to_check(), expect) + self._get_bluestore_compression.side_effect = ValueError + result = self.target.states_to_check() + self.assertIn('fake', result) + self.assertIn('charm.bluestore_compression', result) + + def test_create_pool(self): + ceph_interface = mock.MagicMock() + self.patch_target('_get_bluestore_compression') + self._get_bluestore_compression.side_effect = ValueError + self.target.create_pool(ceph_interface) + self.assertFalse(ceph_interface.create_replicated_pool.called) + self._get_bluestore_compression.side_effect = None + self._get_bluestore_compression.return_value = {'fake': 'value'} + self.target.create_pool(ceph_interface) + ceph_interface.create_replicated_pool.assert_called_once_with( + name='charmname', fake='value') + class TestCephCharm(BaseOpenStackCharmTest):