From 6760bd9d7f83957c9354a2d1251cb494e9937af5 Mon Sep 17 00:00:00 2001 From: Liam Young Date: Thu, 23 Apr 2020 12:04:38 +0000 Subject: [PATCH] Add first set of unit tests --- .gitignore | 3 + .stestr.conf | 3 + ops_openstack.py | 8 +- test-requirements.txt | 8 +- unit_tests/__init__.py | 0 unit_tests/test_ops_openstack.py | 268 +++++++++++++++++++++++++++++++ 6 files changed, 285 insertions(+), 5 deletions(-) create mode 100644 .stestr.conf create mode 100644 unit_tests/__init__.py create mode 100644 unit_tests/test_ops_openstack.py diff --git a/.gitignore b/.gitignore index 172bf57..73a5469 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ .tox +.stestr/ +__pycache__ + diff --git a/.stestr.conf b/.stestr.conf new file mode 100644 index 0000000..5fcccac --- /dev/null +++ b/.stestr.conf @@ -0,0 +1,3 @@ +[DEFAULT] +test_path=./unit_tests +top_dir=./ diff --git a/ops_openstack.py b/ops_openstack.py index c816556..df0099b 100644 --- a/ops_openstack.py +++ b/ops_openstack.py @@ -87,7 +87,9 @@ class OSBaseCharm(CharmBase): return missing_relations = [] for relation in self.REQUIRED_RELATIONS: - if not self.model.get_relation(relation): + try: + self.model.get_relation(relation) + except KeyError: missing_relations.append(relation) if missing_relations: self.unit.status = BlockedStatus( @@ -98,7 +100,7 @@ class OSBaseCharm(CharmBase): # If the check failed the custom check will have set the status. if not self.custom_status_check(): return - except NotImplementedError: + except NotImplementedError: pass if self.state.is_started: self.unit.status = ActiveStatus('Unit is ready') @@ -113,7 +115,7 @@ class OSBaseCharm(CharmBase): _svcs = [] for svc in self.RESTART_MAP.values(): _svcs.extend(svc) - return list(set(_svcs)) + return sorted(list(set(_svcs))) def on_pre_series_upgrade(self, event): _, messages = os_utils.manage_payload_services( diff --git a/test-requirements.txt b/test-requirements.txt index d81ec8a..f66177f 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,5 +1,9 @@ # Lint and unit test requirements -flake8>=2.2.4,<=2.4.1 +flake8 +stestr>=2.2.0 mock>=1.2 -nose>=1.3.7 coverage>=3.6 +# Install netifaces as its a horrible charmhelpers lazy import +netifaces +charmhelpers +git+https://github.com/canonical/operator.git#egg=ops diff --git a/unit_tests/__init__.py b/unit_tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/unit_tests/test_ops_openstack.py b/unit_tests/test_ops_openstack.py new file mode 100644 index 0000000..bfdd1e6 --- /dev/null +++ b/unit_tests/test_ops_openstack.py @@ -0,0 +1,268 @@ +#!/usr/bin/env python3 + +# 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, MagicMock + +from ops.testing import Harness +from ops.model import ( + ActiveStatus, + BlockedStatus, + MaintenanceStatus, + WaitingStatus, +) + +import ops_openstack + + +class OpenStackTestAPICharm(ops_openstack.OSBaseCharm): + + PACKAGES = ['keystone-common'] + REQUIRED_RELATIONS = ['shared-db'] + RESTART_MAP = { + '/etc/f1.conf': ['apache2'], + '/etc/f2.conf': ['apache2', 'ks-api'], + '/etc/f3.conf': []} + + def custom_status_check(self): + if self.model.config.get('custom-check-fail', 'False') == 'True': + self.unit.status = MaintenanceStatus( + 'Custom check failed') + return False + else: + return True + + +class CharmTestCase(unittest.TestCase): + + def setUp(self, obj, patches): + super(CharmTestCase, self).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 TestOSBaseCharm(CharmTestCase): + + PATCHES = [ + 'add_source', + 'apt_update', + 'apt_install', + 'os_utils'] + + def setUp(self): + super(TestOSBaseCharm, self).setUp(ops_openstack, self.PATCHES) + self.os_utils.manage_payload_services = MagicMock() + self.harness = Harness( + OpenStackTestAPICharm, + meta=''' + name: client + provides: + ceph-client: + interface: ceph-client + ''', + actions=''' + pause: + description: pause action + resume: + description: resume action + ''') + + def test_init(self): + self.harness.begin() + self.assertFalse(self.harness.charm.state.is_started) + self.assertFalse(self.harness.charm.state.is_paused) + self.assertFalse(self.harness.charm.state.series_upgrade) + events = list(self.harness.charm.on.events()) + self.assertEqual( + sorted(events), + [ + 'ceph_client_relation_broken', + 'ceph_client_relation_changed', + 'ceph_client_relation_departed', + 'ceph_client_relation_joined', + 'collect_metrics', + 'config_changed', + 'install', + 'leader_elected', + 'leader_settings_changed', + 'pause_action', + 'post_series_upgrade', + 'pre_series_upgrade', + 'remove', + 'resume_action', + 'start', + 'stop', + 'update_status', + 'upgrade_charm']) + + def test_install(self): + self.harness.begin() + self.harness.charm.on_install('An Event') + self.assertFalse(self.add_source.called) + self.apt_update.assert_called_once_with(fatal=True) + self.apt_install.assert_called_once_with( + ['keystone-common'], + fatal=True) + + def test_install_ppa(self): + self.harness.update_config( + key_values={ + 'source': 'cloud:myppa', + 'key': 'akey'}) + self.harness.begin() + self.harness.charm.on_install('An Event') + self.add_source.assert_called_once_with('cloud:myppa', 'akey') + self.apt_update.assert_called_once_with(fatal=True) + self.apt_install.assert_called_once_with( + ['keystone-common'], + fatal=True) + + def test_update_status(self): + self.harness.add_relation('shared-db', 'mysql') + self.harness.begin() + self.harness.charm.state.is_started = True + self.harness.charm.on_update_status('An Event') + self.assertEqual( + self.harness.charm.unit.status.message, + 'Unit is ready') + self.assertIsInstance( + self.harness.charm.unit.status, + ActiveStatus) + + def test_update_status_custom_check_fail(self): + self.harness.update_config( + key_values={ + 'custom-check-fail': 'True'}) + self.harness.add_relation('shared-db', 'mysql') + self.harness.begin() + self.harness.charm.state.is_started = True + self.harness.charm.on_update_status('An Event') + self.assertEqual( + self.harness.charm.unit.status.message, + 'Custom check failed') + self.assertIsInstance( + self.harness.charm.unit.status, + MaintenanceStatus) + + def test_update_status_not_started(self): + self.harness.add_relation('shared-db', 'mysql') + self.harness.begin() + self.harness.charm.on_update_status('An Event') + self.assertEqual( + self.harness.charm.unit.status.message, + 'Charm configuration in progress') + self.assertIsInstance( + self.harness.charm.unit.status, + WaitingStatus) + + def test_update_status_series_upgrade(self): + self.harness.begin() + self.harness.charm.state.series_upgrade = True + self.harness.charm.on_update_status('An Event') + self.assertEqual( + self.harness.charm.unit.status.message, + ('Ready for do-release-upgrade and reboot. Set complete when ' + 'finished.')) + self.assertIsInstance( + self.harness.charm.unit.status, + BlockedStatus) + + def test_update_status_series_paused(self): + self.harness.begin() + self.harness.charm.state.is_paused = True + self.harness.charm.on_update_status('An Event') + self.assertEqual( + self.harness.charm.unit.status.message, + "Paused. Use 'resume' action to resume normal service.") + self.assertIsInstance( + self.harness.charm.unit.status, + MaintenanceStatus) + + def test_update_status_missing_relation(self): + self.harness.begin() + self.harness.charm.on_update_status('An Event') + self.assertEqual( + self.harness.charm.unit.status.message, + 'Missing relations: shared-db') + self.assertIsInstance( + self.harness.charm.unit.status, + BlockedStatus) + + def test_services(self): + self.harness.begin() + self.assertEqual( + self.harness.charm.services(), + ['apache2', 'ks-api']) + + def test_pre_series_upgrade(self): + self.os_utils.manage_payload_services.return_value = ('a', 'b') + self.harness.begin() + self.assertFalse(self.harness.charm.state.series_upgrade) + self.assertFalse(self.harness.charm.state.is_paused) + self.harness.charm.on_pre_series_upgrade('An Event') + self.assertTrue(self.harness.charm.state.series_upgrade) + self.assertTrue(self.harness.charm.state.is_paused) + self.os_utils.manage_payload_services.assert_called_once_with( + 'pause', + services=['apache2', 'ks-api'], + charm_func=None) + + def test_post_series_upgrade(self): + self.os_utils.manage_payload_services.return_value = ('a', 'b') + self.harness.begin() + self.harness.charm.state.series_upgrade = True + self.harness.charm.state.is_paused = True + self.harness.charm.on_post_series_upgrade('An Event') + self.assertFalse(self.harness.charm.state.series_upgrade) + self.assertFalse(self.harness.charm.state.is_paused) + self.os_utils.manage_payload_services.assert_called_once_with( + 'resume', + services=['apache2', 'ks-api'], + charm_func=None) + + def test_pause(self): + self.os_utils.manage_payload_services.return_value = ('a', 'b') + self.harness.begin() + self.assertFalse(self.harness.charm.state.is_paused) + self.harness.charm.on_pause_action('An Event') + self.assertTrue(self.harness.charm.state.is_paused) + self.os_utils.manage_payload_services.assert_called_once_with( + 'pause', + services=['apache2', 'ks-api'], + charm_func=None) + + def test_resume(self): + self.os_utils.manage_payload_services.return_value = ('a', 'b') + self.harness.begin() + self.harness.charm.state.is_paused = True + self.harness.charm.on_resume_action('An Event') + self.assertFalse(self.harness.charm.state.is_paused) + self.os_utils.manage_payload_services.assert_called_once_with( + 'resume', + services=['apache2', 'ks-api'], + charm_func=None)