diff --git a/ops_openstack/core.py b/ops_openstack/core.py index a58bc12..7ce861e 100644 --- a/ops_openstack/core.py +++ b/ops_openstack/core.py @@ -1,3 +1,17 @@ +# Copyright 2020 Canonical Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + from ops.charm import CharmBase from ops.framework import ( StoredState, diff --git a/ops_openstack/plugins/__init__.py b/ops_openstack/plugins/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ops_openstack/plugins/classes.py b/ops_openstack/plugins/classes.py new file mode 100644 index 0000000..a4ed5ab --- /dev/null +++ b/ops_openstack/plugins/classes.py @@ -0,0 +1,55 @@ +# Copyright 2020 Canonical Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import ops_openstack.core +# ch_context needed for bluestore validation +import charmhelpers.contrib.openstack.context as ch_context +from ops.model import ( + ActiveStatus, + BlockedStatus, +) + + +class BaseCephClientCharm(ops_openstack.core.OSBaseCharm): + + def __init__(self, framework): + super().__init__(framework) + super().register_status_check(self.check_bluestore_compression) + + def check_bluestore_compression(self): + try: + self.get_bluestore_compression() + return ActiveStatus() + except ValueError as e: + return BlockedStatus( + 'Invalid configuration: {}'.format(str(e))) + + @staticmethod + def get_bluestore_compression(): + """Get BlueStore Compression charm configuration if present. + + :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() diff --git a/test-requirements.txt b/test-requirements.txt index f66177f..3a760ea 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -5,5 +5,6 @@ mock>=1.2 coverage>=3.6 # Install netifaces as its a horrible charmhelpers lazy import netifaces +psutil charmhelpers git+https://github.com/canonical/operator.git#egg=ops diff --git a/unit_tests/test_plugins_classes.py b/unit_tests/test_plugins_classes.py new file mode 100644 index 0000000..e6d5038 --- /dev/null +++ b/unit_tests/test_plugins_classes.py @@ -0,0 +1,105 @@ +# Copyright 2020 Canonical Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import unittest + +from mock import patch + +from ops.testing import Harness +from ops.model import ( + ActiveStatus, + BlockedStatus, +) + +import ops_openstack.plugins.classes + + +class CephTestCharm(ops_openstack.plugins.classes.BaseCephClientCharm): + pass + + +class CharmTestCase(unittest.TestCase): + + def setUp(self, obj, patches): + super().setUp() + self.patches = patches + self.obj = obj + self.patch_all() + + def patch(self, method): + _m = patch.object(self.obj, method) + mock = _m.start() + self.addCleanup(_m.stop) + return mock + + def patch_all(self): + for method in self.patches: + setattr(self, method, self.patch(method)) + + +class TestBaseCephClientCharm(CharmTestCase): + + PATCHES = [ + 'ch_context'] + + def setUp(self): + super().setUp(ops_openstack.plugins.classes, self.PATCHES) + self.harness = Harness( + CephTestCharm, + meta=''' + name: client + requires: + shared-db: + interface: mysql-shared + provides: + ceph-client: + interface: ceph-client + ''', + actions=''' + pause: + description: pause action + resume: + description: resume action + ''') + self.harness.add_relation('shared-db', 'mysql') + + def test_update_status(self): + self.harness.begin() + self.harness.charm._stored.is_started = True + self.harness.charm.on.update_status.emit() + self.assertEqual( + self.harness.charm.unit.status.message, + 'Unit is ready') + self.assertIsInstance( + self.harness.charm.unit.status, + ActiveStatus) + + def test_update_status_invalid_config(self): + + class BlueCtxt(): + + def validate(self): + raise ValueError('BadKey') + + self.ch_context.CephBlueStoreCompressionContext = BlueCtxt + + self.harness.begin() + self.harness.charm._stored.is_started = True + self.harness.charm.on.update_status.emit() + self.assertEqual( + self.harness.charm.unit.status.message, + 'Invalid configuration: BadKey') + self.assertIsInstance( + self.harness.charm.unit.status, + BlockedStatus)