From 639fb975f9e6ba218c99f3124eec8ce08b397bfc Mon Sep 17 00:00:00 2001
From: Marius <moprin@cloudbasesolutions.com>
Date: Thu, 11 Mar 2021 10:25:34 +0200
Subject: [PATCH] Add unit_tests

---
 .stestr.conf                       |   3 +
 src/reactive/magnum_handlers.py    |  11 ++-
 unit_tests/__init__.py             |  72 ++++++++++++++++
 unit_tests/test_magnum_handlers.py | 129 +++++++++++++++++++++++++++++
 4 files changed, 214 insertions(+), 1 deletion(-)
 create mode 100644 .stestr.conf
 create mode 100644 unit_tests/test_magnum_handlers.py

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/src/reactive/magnum_handlers.py b/src/reactive/magnum_handlers.py
index 566b0f8..46117e8 100644
--- a/src/reactive/magnum_handlers.py
+++ b/src/reactive/magnum_handlers.py
@@ -28,6 +28,7 @@ charm.use_defaults(
 @reactive.when('shared-db.available')
 @reactive.when('identity-service.available')
 @reactive.when('amqp.available')
+@reactive.when_not('is-update-status-hook')
 def render_config(*interfaces):
     with charm.provide_charm_instance() as magnum_charm:
         magnum_charm.render_with_interfaces(interfaces)
@@ -39,21 +40,26 @@ def render_config(*interfaces):
 @reactive.when('shared-db.available')
 @reactive.when('identity-service.available')
 @reactive.when('amqp.available')
+@reactive.when_not('is-update-status-hook')
 def render_config_with_certs(amqp, keystone, shared_db, certs):
     with charm.provide_charm_instance() as magnum_charm:
         magnum_charm.configure_tls(certs)
         magnum_charm.render_with_interfaces(
             [amqp, keystone, shared_db, certs])
+        magnum_charm.assess_status()
+    reactive.set_state('config.complete')
 
 
 @reactive.when('identity-service.connected')
+@reactive.when_not('is-update-status-hook')
 def setup_endpoint(keystone):
     magnum.setup_endpoint(keystone)
     magnum.assess_status()
 
 
-@reactive.when_not('leadership.set.magnum_password')
 @reactive.when('leadership.is_leader')
+@reactive.when_not('leadership.set.magnum_password')
+@reactive.when_not('is-update-status-hook')
 def generate_magnum_password():
     passwd = binascii.b2a_hex(os.urandom(32)).decode()
     leadership.leader_set({'magnum_password': passwd})
@@ -62,6 +68,7 @@ def generate_magnum_password():
 @reactive.when('leadership.set.magnum_password')
 @reactive.when('leadership.is_leader')
 @reactive.when('identity-service.available')
+@reactive.when_not('is-update-status-hook')
 def write_openrc():
     config = hookenv.config()
     ctx = context.IdentityServiceContext()()
@@ -73,6 +80,7 @@ def write_openrc():
 
 @reactive.when('config.complete')
 @reactive.when_not('db.synced')
+@reactive.when_not('is-update-status-hook')
 def run_db_migration():
     with charm.provide_charm_instance() as magnum_charm:
         magnum_charm.db_sync()
@@ -83,6 +91,7 @@ def run_db_migration():
 
 @reactive.when('ha.connected')
 @reactive.when_not('ha.available')
+@reactive.when_not('is-update-status-hook')
 def connect_cluster(hacluster):
     with charm.provide_charm_instance() as magnum_charm:
         magnum_charm.configure_ha_resources(hacluster)
diff --git a/unit_tests/__init__.py b/unit_tests/__init__.py
index e69de29..3836348 100644
--- a/unit_tests/__init__.py
+++ b/unit_tests/__init__.py
@@ -0,0 +1,72 @@
+# Copyright 2021 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 sys
+
+sys.path.append('src')
+sys.path.append('src/lib')
+
+# Mock out charmhelpers so that we can test without it.
+import charms_openstack.test_mocks  # noqa
+charms_openstack.test_mocks.mock_charmhelpers()
+
+import mock
+
+
+class _fake_decorator(object):
+
+    def __init__(self, *args):
+        pass
+
+    def __call__(self, f):
+        return f
+
+
+charms = mock.MagicMock()
+sys.modules['charms'] = charms
+charms.leadership = mock.MagicMock()
+sys.modules['charms.leadership'] = charms.leadership
+charms.reactive = mock.MagicMock()
+charms.reactive.when = _fake_decorator
+charms.reactive.when_all = _fake_decorator
+charms.reactive.when_any = _fake_decorator
+charms.reactive.when_not = _fake_decorator
+charms.reactive.when_none = _fake_decorator
+charms.reactive.when_not_all = _fake_decorator
+charms.reactive.not_unless = _fake_decorator
+charms.reactive.when_file_changed = _fake_decorator
+charms.reactive.collect_metrics = _fake_decorator
+charms.reactive.meter_status_changed = _fake_decorator
+charms.reactive.only_once = _fake_decorator
+charms.reactive.hook = _fake_decorator
+charms.reactive.bus = mock.MagicMock()
+charms.reactive.flags = mock.MagicMock()
+charms.reactive.relations = mock.MagicMock()
+sys.modules['charms.reactive'] = charms.reactive
+sys.modules['charms.reactive.bus'] = charms.reactive.bus
+sys.modules['charms.reactive.bus'] = charms.reactive.decorators
+sys.modules['charms.reactive.flags'] = charms.reactive.flags
+sys.modules['charms.reactive.relations'] = charms.reactive.relations
+keystoneauth1 = mock.MagicMock()
+sys.modules['keystoneauth1'] = keystoneauth1
+netaddr = mock.MagicMock()
+sys.modules['netaddr'] = netaddr
+novaclient = mock.MagicMock()
+sys.modules['novaclient'] = novaclient
+neutron_lib = mock.MagicMock()
+sys.modules['neutron_lib'] = neutron_lib
+sys.modules['neutron_lib.constants'] = neutron_lib.constants
+neutronclient = mock.MagicMock()
+sys.modules['neutronclient'] = neutronclient
+sys.modules['neutronclient.v2_0'] = neutronclient.v2_0
diff --git a/unit_tests/test_magnum_handlers.py b/unit_tests/test_magnum_handlers.py
new file mode 100644
index 0000000..83bd87e
--- /dev/null
+++ b/unit_tests/test_magnum_handlers.py
@@ -0,0 +1,129 @@
+# Copyright 2021 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 mock
+
+import charm.openstack.magnum.magnum as magnum
+import reactive.magnum_handlers as handlers
+
+import charms_openstack.test_utils as test_utils
+
+
+class TestRegisteredHooks(test_utils.TestRegisteredHooks):
+
+    def test_hooks(self):
+        defaults = [
+            'charm.installed',
+            'amqp.connected',
+            'shared-db.connected',
+            'identity-service.connected',
+            'config.changed',
+            'update-status',
+            'upgrade-charm',
+            'certificates.available',
+        ]
+        hook_set = {
+            'when': {
+                'render_config': (
+                    'shared-db.available',
+                    'identity-service.available',
+                    'amqp.available',),
+                'render_config_with_certs': (
+                    'certificates.available',
+                    'shared-db.available',
+                    'identity-service.available',
+                    'amqp.available',),
+                'run_db_migration': ('config.complete',),
+                'connect_cluster': ('ha.connected',),
+                'generate_magnum_password': ('leadership.is_leader',),
+                'generate_heartbeat_key': ('leadership.is_leader',),
+                'setup_endpoint': ('identity-service.connected',),
+                'write_openrc': (
+                    'leadership.set.magnum_password',
+                    'leadership.is_leader',
+                    'identity-service.available',),
+            },
+            'when_not': {
+                'run_db_migration': ('db.synced', 'is-update-status-hook'),
+                'connect_cluster': ('ha.available', 'is-update-status-hook'),
+                'generate_heartbeat_key': ('leadership.set.heartbeat-key',
+                                           'is-update-status-hook'),
+                'generate_magnum_password': ('leadership.set.magnum_password',
+                                             'is-update-status-hook'),
+                'setup_endpoint': ('is-update-status-hook',),
+                'render_config': ('is-update-status-hook',),
+                'render_config_with_certs': ('is-update-status-hook',),
+                'write_openrc': ('is-update-status-hook',),
+            },
+        }
+        # test that the hooks were registered via the
+        # reactive.magnum_handlers
+        self.registered_hooks_test_helper(handlers, hook_set, defaults)
+
+
+class TestMagnumHandlers(test_utils.PatchHelper):
+
+    def setUp(self):
+        super().setUp()
+        self.patch_release(magnum.MagnumCharm.release)
+        self.magnum_charm = mock.MagicMock()
+        self.patch_object(handlers.charm, 'provide_charm_instance',
+                          new=mock.MagicMock())
+        self.provide_charm_instance().__enter__.return_value = \
+            self.magnum_charm
+        self.provide_charm_instance().__exit__.return_value = None
+
+    def test_setup_endpoint(self):
+        keystone = mock.MagicMock()
+        charm = magnum.MagnumCharm.singleton
+        handlers.setup_endpoint(keystone)
+        keystone.register_endpoints.assert_called_once_with(
+            charm.service_type,
+            charm.region,
+            '{}/v1'.format(charm.public_url),
+            '{}/v1'.format(charm.internal_url),
+            '{}/v1'.format(charm.admin_url)
+        )
+
+    def test_render_config(self):
+        self.patch('charms.reactive.set_state', 'set_state')
+        handlers.render_config('arg1', 'arg2')
+        self.magnum_charm.render_with_interfaces.assert_called_once_with(
+            ('arg1', 'arg2'))
+        self.magnum_charm.assess_status.assert_called_once_with()
+        self.set_state.assert_called_once_with('config.complete')
+
+    def test_render_config_with_certs(self):
+        self.patch('charms.reactive.set_state', 'set_state')
+        handlers.render_config_with_certs('arg1', 'arg2', 'arg3', 'arg4')
+        self.magnum_charm.configure_tls.assert_called_once_with('arg4')
+        self.magnum_charm.render_with_interfaces.assert_called_once_with(
+            ['arg1', 'arg2', 'arg3', 'arg4'])
+        self.magnum_charm.assess_status.assert_called_once_with()
+        self.set_state.assert_called_once_with('config.complete')
+
+    def test_run_db_migration(self):
+        self.patch('charms.reactive.set_state', 'set_state')
+        handlers.run_db_migration()
+        self.magnum_charm.db_sync.assert_called_once_with()
+        self.magnum_charm.restart_all.assert_called_once_with()
+        self.set_state.assert_called_once_with('db.synced')
+        self.magnum_charm.assess_status.assert_called_once_with()
+
+    def test_connect_cluster(self):
+        hacluster = mock.MagicMock()
+        handlers.connect_cluster(hacluster)
+        self.magnum_charm.configure_ha_resources.assert_called_once_with(
+            hacluster)
+        self.magnum_charm.assess_status.assert_called_once_with()