From b00cb733a89cd14625db45f6f747fec8abc8a7ab Mon Sep 17 00:00:00 2001 From: Alex Kavanagh Date: Wed, 5 Oct 2016 12:21:26 +0000 Subject: [PATCH] Fix func27-smoke which fails This was a combination of the smoke test being incorrectly specified and there being no assess_status() at the end of the hsm_connected() function. Also the assess_status() was removed from the layer-openstack which meant that the status wasn't updating. The change adds in an assess_status() call, but also reworks the tests so that they use the new features in charms.openstack, and also uses defaults for config-changed and assess-status hooks/events. The gate that tox.ini is the keystone-v2 version of the tests. Change-Id: Ib8082a083b05eba872d8a2e5fe477352df25ccbb Closes-Bug: #1629624 --- src/lib/charm/openstack/softhsm.py | 10 ++ src/reactive/handlers.py | 8 ++ src/tests/README.md | 2 + ...a-keystone-v2 => gate-basic-xenial-mitaka} | 0 unit_tests/test_handlers.py | 124 ++++-------------- unit_tests/test_lib_barbican_softhsm.py | 96 ++++++-------- 6 files changed, 83 insertions(+), 157 deletions(-) rename src/tests/{gate-basic-xenial-mitaka-keystone-v2 => gate-basic-xenial-mitaka} (100%) diff --git a/src/lib/charm/openstack/softhsm.py b/src/lib/charm/openstack/softhsm.py index 2259734..bd96f35 100644 --- a/src/lib/charm/openstack/softhsm.py +++ b/src/lib/charm/openstack/softhsm.py @@ -42,9 +42,19 @@ def install(): def on_hsm_connected(hsm): + """When SoftHSM connects to Barbican, configure Barbican with the + information necessary to configure the plugin. + + :param hsm: the hsm relation object + """ BarbicanSoftHSMCharm.singleton.on_hsm_connected(hsm) +def assess_status(): + """Call the charm assess_status function""" + BarbicanSoftHSMCharm.singleton.assess_status() + + class BarbicanSoftHSMCharm(charms_openstack.charm.OpenStackCharm): service_name = 'barbican-softhsm' diff --git a/src/reactive/handlers.py b/src/reactive/handlers.py index 504de1d..d75e293 100644 --- a/src/reactive/handlers.py +++ b/src/reactive/handlers.py @@ -14,8 +14,15 @@ import charms.reactive as reactive +import charms_openstack.charm + import charm.openstack.softhsm as softhsm +# Use the charms.openstack defaults for common states and hooks +charms_openstack.charm.use_defaults( + 'config.changed', + 'update-status') + # use a synthetic state to ensure that it get it to be installed independent of # the install hook. @@ -29,3 +36,4 @@ def install_packages(): def hsm_connected(hsm): softhsm.on_hsm_connected(hsm) reactive.set_state('hsm.available') + softhsm.assess_status() diff --git a/src/tests/README.md b/src/tests/README.md index 8a856d6..ebbda72 100644 --- a/src/tests/README.md +++ b/src/tests/README.md @@ -1 +1,3 @@ TODO: write Amulet tests. + +Note that gate-basic-xenial-mitaka is the keystone V2 version of the tests. diff --git a/src/tests/gate-basic-xenial-mitaka-keystone-v2 b/src/tests/gate-basic-xenial-mitaka similarity index 100% rename from src/tests/gate-basic-xenial-mitaka-keystone-v2 rename to src/tests/gate-basic-xenial-mitaka diff --git a/unit_tests/test_handlers.py b/unit_tests/test_handlers.py index ebafa53..20b3396 100644 --- a/unit_tests/test_handlers.py +++ b/unit_tests/test_handlers.py @@ -15,118 +15,44 @@ from __future__ import absolute_import from __future__ import print_function -import unittest - -import mock - import reactive.handlers as handlers - -_when_args = {} -_when_not_args = {} +import charms_openstack.test_utils as test_utils -def mock_hook_factory(d): +class TestRegisteredHooks(test_utils.TestRegisteredHooks): - def mock_hook(*args, **kwargs): - - def inner(f): - # remember what we were passed. Note that we can't actually - # determine the class we're attached to, as the decorator only gets - # the function. - try: - d[f.__name__].append(dict(args=args, kwargs=kwargs)) - except KeyError: - d[f.__name__] = [dict(args=args, kwargs=kwargs)] - return f - return inner - return mock_hook - - -class TestBarbicanHandlers(unittest.TestCase): - - @classmethod - def setUpClass(cls): - cls._patched_when = mock.patch('charms.reactive.when', - mock_hook_factory(_when_args)) - cls._patched_when_started = cls._patched_when.start() - cls._patched_when_not = mock.patch('charms.reactive.when_not', - mock_hook_factory(_when_not_args)) - cls._patched_when_not_started = cls._patched_when_not.start() - # force requires to rerun the mock_hook decorator: - # try except is Python2/Python3 compatibility as Python3 has moved - # reload to importlib. - try: - reload(handlers) - except NameError: - import importlib - importlib.reload(handlers) - - @classmethod - def tearDownClass(cls): - cls._patched_when.stop() - cls._patched_when_started = None - cls._patched_when = None - cls._patched_when_not.stop() - cls._patched_when_not_started = None - cls._patched_when_not = None - # and fix any breakage we did to the module - try: - reload(handlers) - except NameError: - import importlib - importlib.reload(handlers) - - def setUp(self): - self._patches = {} - self._patches_start = {} - - def tearDown(self): - for k, v in self._patches.items(): - v.stop() - setattr(self, k, None) - self._patches = None - self._patches_start = None - - def patch(self, obj, attr, return_value=None): - mocked = mock.patch.object(obj, attr) - self._patches[attr] = mocked - started = mocked.start() - started.return_value = return_value - self._patches_start[attr] = started - setattr(self, attr, started) - - def test_registered_hooks(self): - # test that the hooks actually registered the relation expressions that - # are meaningful for this interface: this is to handle regressions. - # The keys are the function names that the hook attaches to. - when_patterns = { - 'hsm_connected': ('hsm.connected', ), + def test_hooks(self): + defaults = [ + 'config.changed', + 'update-status'] + hook_set = { + 'when': { + 'hsm_connected': ('hsm.connected', ), + }, + 'when_not': { + 'install_packages': ('charm.installed', ), + } } - when_not_patterns = { - 'install_packages': ('charm.installed', ), - } - # check the when hooks are attached to the expected functions - for t, p in [(_when_args, when_patterns), - (_when_not_args, when_not_patterns)]: - for f, args in t.items(): - # check that function is in patterns - # print("f: {}, args: {}".format(f, args)) - self.assertTrue(f in p.keys()) - # check that the lists are equal - l = [a['args'][0] for a in args] - self.assertEqual(l, sorted(p[f])) + # test that the hooks were registered via the + # reactive.barbican_handlers + self.registered_hooks_test_helper(handlers, hook_set, defaults) + + +class TestBarbicanHandlers(test_utils.PatchHelper): def test_install_packages(self): - self.patch(handlers.softhsm, 'install') - self.patch(handlers.reactive, 'set_state') + self.patch_object(handlers.softhsm, 'install') + self.patch_object(handlers.reactive, 'set_state') handlers.install_packages() self.install.assert_called_once_with() self.set_state.assert_called_once_with('charm.installed') def test_hsm_connected(self): - self.patch(handlers.softhsm, 'on_hsm_connected') - self.patch(handlers.reactive, 'set_state') + self.patch_object(handlers.softhsm, 'on_hsm_connected') + self.patch_object(handlers.reactive, 'set_state') + self.patch_object(handlers.softhsm, 'assess_status') handlers.hsm_connected('hsm-thing') self.on_hsm_connected.assert_called_once_with('hsm-thing') self.set_state.assert_called_once_with('hsm.available') + self.assess_status.assert_called_once_with() diff --git a/unit_tests/test_lib_barbican_softhsm.py b/unit_tests/test_lib_barbican_softhsm.py index 8e6a37f..404b3ba 100644 --- a/unit_tests/test_lib_barbican_softhsm.py +++ b/unit_tests/test_lib_barbican_softhsm.py @@ -16,53 +16,33 @@ from __future__ import absolute_import from __future__ import print_function import textwrap -import unittest import mock import charm.openstack.softhsm as softhsm - -class Helper(unittest.TestCase): - - def setUp(self): - self._patches = {} - self._patches_start = {} - # patch out the select_release to always return 'mitaka' - # self.patch(softhsm.unitdata, 'kv') - # _getter = mock.MagicMock() - # _getter.get.return_value = softhsm.BarbicanSoftHSMCharm.release - # self.kv.return_value = _getter - - def tearDown(self): - for k, v in self._patches.items(): - v.stop() - setattr(self, k, None) - self._patches = None - self._patches_start = None - - def patch(self, obj, attr, return_value=None, **kwargs): - mocked = mock.patch.object(obj, attr, **kwargs) - self._patches[attr] = mocked - started = mocked.start() - started.return_value = return_value - self._patches_start[attr] = started - setattr(self, attr, started) +import charms_openstack.test_utils as test_utils -class TestSoftHSM(Helper): +class TestSoftHSM(test_utils.PatchHelper): def test_install(self): - self.patch(softhsm.BarbicanSoftHSMCharm.singleton, 'install') + self.patch_object(softhsm.BarbicanSoftHSMCharm.singleton, 'install') softhsm.install() self.install.assert_called_once_with() def test_on_hsm_connected(self): - self.patch(softhsm.BarbicanSoftHSMCharm.singleton, - 'on_hsm_connected') + self.patch_object(softhsm.BarbicanSoftHSMCharm.singleton, + 'on_hsm_connected') softhsm.on_hsm_connected('hsm-thing') self.on_hsm_connected.assert_called_once_with('hsm-thing') + def test_assess_status(self): + self.patch_object(softhsm.BarbicanSoftHSMCharm.singleton, + 'assess_status') + softhsm.assess_status() + self.assess_status.assert_called_once_with() + def test_read_pins_from_store(self): # test with no file (patch open so that it raises an error) mock_open = mock.MagicMock(return_value=mock.sentinel.file_handle) @@ -84,9 +64,9 @@ class TestSoftHSM(Helper): def test_write_pins_to_store(self): f = mock.MagicMock() - self.patch(softhsm.os, 'fdopen', return_value=f) - self.patch(softhsm.os, 'open', return_value='opener') - self.patch(softhsm.json, 'dump') + self.patch_object(softhsm.os, 'fdopen', return_value=f) + self.patch_object(softhsm.os, 'open', return_value='opener') + self.patch_object(softhsm.json, 'dump') softhsm.write_pins_to_store('1234', '5678') self.open.assert_called_once_with( softhsm.STORED_PINS_FILE, @@ -115,23 +95,23 @@ class TestSoftHSM(Helper): User PIN init.: yes Label: barbican_token """) - self.patch(softhsm.subprocess, 'check_output', - return_value=result.encode()) + self.patch_object(softhsm.subprocess, 'check_output', + return_value=result.encode()) self.assertEqual(softhsm.read_slot_id('barbican_token'), '5') self.check_output.assert_called_once_with( [softhsm.SOFTHSM2_UTIL_CMD, '--show-slots']) self.assertEqual(softhsm.read_slot_id('not_found'), None) -class TestBarbicanSoftHSMCharm(Helper): +class TestBarbicanSoftHSMCharm(test_utils.PatchHelper): def test_install(self): - self.patch(softhsm.charms_openstack.charm.OpenStackCharm, - 'install') - self.patch(softhsm.ch_core_host, 'add_user_to_group') + self.patch_object(softhsm.charms_openstack.charm.OpenStackCharm, + 'install') + self.patch_object(softhsm.ch_core_host, 'add_user_to_group') c = softhsm.BarbicanSoftHSMCharm() - self.patch(c, 'setup_token_store') - self.patch(softhsm.hookenv, 'status_set') + self.patch_object(c, 'setup_token_store') + self.patch_object(softhsm.hookenv, 'status_set') c.install() self.install.assert_called_once_with() self.add_user_to_group.assert_called_once_with('barbican', 'softhsm') @@ -140,17 +120,17 @@ class TestBarbicanSoftHSMCharm(Helper): 'waiting', 'Charm installed and token store configured') def test_setup_token_store(self): - self.patch(softhsm, 'read_pins_from_store') - self.patch(softhsm.os.path, 'exists') - self.patch(softhsm.os.path, 'isdir') - self.patch(softhsm.shutil, 'rmtree') - self.patch(softhsm.os, 'remove') - self.patch(softhsm.os, 'makedirs') - self.patch(softhsm.os, 'chmod') - self.patch(softhsm.ch_core_host, 'pwgen') - self.patch(softhsm, 'write_pins_to_store') - self.patch(softhsm.subprocess, 'check_call') - self.patch(softhsm.hookenv, 'log') + self.patch_object(softhsm, 'read_pins_from_store') + self.patch_object(softhsm.os.path, 'exists') + self.patch_object(softhsm.os.path, 'isdir') + self.patch_object(softhsm.shutil, 'rmtree') + self.patch_object(softhsm.os, 'remove') + self.patch_object(softhsm.os, 'makedirs') + self.patch_object(softhsm.os, 'chmod') + self.patch_object(softhsm.ch_core_host, 'pwgen') + self.patch_object(softhsm, 'write_pins_to_store') + self.patch_object(softhsm.subprocess, 'check_call') + self.patch_object(softhsm.hookenv, 'log') # first, pretend that the token store is already setup. self.read_pins_from_store.return_value = ('1234', '5678', ) c = softhsm.BarbicanSoftHSMCharm() @@ -183,12 +163,12 @@ class TestBarbicanSoftHSMCharm(Helper): def test_on_hsm_connected(self): hsm = mock.MagicMock() - self.patch(softhsm, 'read_pins_from_store') - self.patch(softhsm, 'read_slot_id') - self.patch(softhsm.hookenv, 'status_set') - self.patch(softhsm.hookenv, 'log') + self.patch_object(softhsm, 'read_pins_from_store') + self.patch_object(softhsm, 'read_slot_id') + self.patch_object(softhsm.hookenv, 'status_set') + self.patch_object(softhsm.hookenv, 'log') c = softhsm.BarbicanSoftHSMCharm() - self.patch(c, 'setup_token_store') + self.patch_object(c, 'setup_token_store') # simulate not being able to set up the token store self.read_pins_from_store.return_value = None, None with self.assertRaises(RuntimeError):