diff options
Diffstat (limited to 'charms/trusty/ceilometer/unit_tests')
7 files changed, 959 insertions, 0 deletions
diff --git a/charms/trusty/ceilometer/unit_tests/__init__.py b/charms/trusty/ceilometer/unit_tests/__init__.py new file mode 100644 index 0000000..53a4570 --- /dev/null +++ b/charms/trusty/ceilometer/unit_tests/__init__.py @@ -0,0 +1,3 @@ +import sys +sys.path.append('actions') +sys.path.append('hooks') diff --git a/charms/trusty/ceilometer/unit_tests/test_actions.py b/charms/trusty/ceilometer/unit_tests/test_actions.py new file mode 100644 index 0000000..67643c8 --- /dev/null +++ b/charms/trusty/ceilometer/unit_tests/test_actions.py @@ -0,0 +1,64 @@ +import mock +from mock import patch + +from test_utils import CharmTestCase + +with patch('ceilometer_utils.register_configs') as configs: + configs.return_value = 'test-config' + import actions + + +class PauseTestCase(CharmTestCase): + + def setUp(self): + super(PauseTestCase, self).setUp( + actions, ["pause_unit_helper"]) + + def test_pauses_services(self): + actions.pause([]) + self.pause_unit_helper.assert_called_once_with('test-config') + + +class ResumeTestCase(CharmTestCase): + + def setUp(self): + super(ResumeTestCase, self).setUp( + actions, ["resume_unit_helper"]) + + def test_pauses_services(self): + actions.resume([]) + self.resume_unit_helper.assert_called_once_with('test-config') + + +class MainTestCase(CharmTestCase): + + def setUp(self): + super(MainTestCase, self).setUp(actions, ["action_fail"]) + + def test_invokes_action(self): + dummy_calls = [] + + def dummy_action(args): + dummy_calls.append(True) + + with mock.patch.dict(actions.ACTIONS, {"foo": dummy_action}): + actions.main(["foo"]) + self.assertEqual(dummy_calls, [True]) + + def test_unknown_action(self): + """Unknown actions aren't a traceback.""" + exit_string = actions.main(["foo"]) + self.assertEqual("Action foo undefined", exit_string) + + def test_failing_action(self): + """Actions which traceback trigger action_fail() calls.""" + dummy_calls = [] + + self.action_fail.side_effect = dummy_calls.append + + def dummy_action(args): + raise ValueError("uh oh") + + with mock.patch.dict(actions.ACTIONS, {"foo": dummy_action}): + actions.main(["foo"]) + self.assertEqual(dummy_calls, ["uh oh"]) diff --git a/charms/trusty/ceilometer/unit_tests/test_actions_openstack_upgrade.py b/charms/trusty/ceilometer/unit_tests/test_actions_openstack_upgrade.py new file mode 100644 index 0000000..3babe21 --- /dev/null +++ b/charms/trusty/ceilometer/unit_tests/test_actions_openstack_upgrade.py @@ -0,0 +1,67 @@ +import os +import sys + +from mock import patch, MagicMock + +# python-apt is not installed as part of test-requirements but is imported by +# some charmhelpers modules so create a fake import. +mock_apt = MagicMock() +sys.modules['apt'] = mock_apt +mock_apt.apt_pkg = MagicMock() + +os.environ['JUJU_UNIT_NAME'] = 'ceilometer' + +with patch('charmhelpers.contrib.hardening.harden.harden') as mock_dec: + mock_dec.side_effect = (lambda *dargs, **dkwargs: lambda f: + lambda *args, **kwargs: f(*args, **kwargs)) + with patch('ceilometer_utils.register_configs') as register_configs: + with patch('ceilometer_utils.ceilometer_release_services'): + import openstack_upgrade + +from test_utils import ( + CharmTestCase +) + +TO_PATCH = [ + 'config_changed', + 'do_openstack_upgrade', +] + + +class TestCeilometerUpgradeActions(CharmTestCase): + + def setUp(self): + super(TestCeilometerUpgradeActions, self).setUp(openstack_upgrade, + TO_PATCH) + + @patch('charmhelpers.contrib.openstack.utils.juju_log') + @patch('charmhelpers.contrib.openstack.utils.config') + @patch('charmhelpers.contrib.openstack.utils.action_set') + @patch('charmhelpers.contrib.openstack.utils.git_install_requested') + @patch('charmhelpers.contrib.openstack.utils.openstack_upgrade_available') + def test_openstack_upgrade_true(self, upgrade_avail, git_requested, + action_set, config, log): + git_requested.return_value = False + upgrade_avail.return_value = True + config.return_value = True + + openstack_upgrade.openstack_upgrade() + + self.assertTrue(self.do_openstack_upgrade.called) + self.assertTrue(self.config_changed.called) + + @patch('charmhelpers.contrib.openstack.utils.juju_log') + @patch('charmhelpers.contrib.openstack.utils.config') + @patch('charmhelpers.contrib.openstack.utils.action_set') + @patch('charmhelpers.contrib.openstack.utils.git_install_requested') + @patch('charmhelpers.contrib.openstack.utils.openstack_upgrade_available') + def test_openstack_upgrade_false(self, upgrade_avail, git_requested, + action_set, config, log): + git_requested.return_value = False + upgrade_avail.return_value = True + config.return_value = False + + openstack_upgrade.openstack_upgrade() + + self.assertFalse(self.do_openstack_upgrade.called) + self.assertFalse(self.config_changed.called) diff --git a/charms/trusty/ceilometer/unit_tests/test_ceilometer_contexts.py b/charms/trusty/ceilometer/unit_tests/test_ceilometer_contexts.py new file mode 100644 index 0000000..038dfd0 --- /dev/null +++ b/charms/trusty/ceilometer/unit_tests/test_ceilometer_contexts.py @@ -0,0 +1,164 @@ +from mock import patch + +import ceilometer_contexts as contexts +import ceilometer_utils as utils + +from test_utils import CharmTestCase, mock_open + +TO_PATCH = [ + 'config', + 'relation_get', + 'relation_ids', + 'related_units', + 'os_release', +] + + +class CeilometerContextsTest(CharmTestCase): + + def setUp(self): + super(CeilometerContextsTest, self).setUp(contexts, TO_PATCH) + self.config.side_effect = self.test_config.get + self.relation_get.side_effect = self.test_relation.get + + def tearDown(self): + super(CeilometerContextsTest, self).tearDown() + + def test_logging_context(self): + self.test_config.set('debug', False) + self.test_config.set('verbose', False) + self.assertEquals(contexts.LoggingConfigContext()(), + {'debug': False, 'verbose': False}) + self.test_config.set('debug', True) + self.test_config.set('verbose', False) + self.assertEquals(contexts.LoggingConfigContext()(), + {'debug': True, 'verbose': False}) + self.test_config.set('debug', True) + self.test_config.set('verbose', True) + self.assertEquals(contexts.LoggingConfigContext()(), + {'debug': True, 'verbose': True}) + + def test_mongodb_context_not_related(self): + self.relation_ids.return_value = [] + self.os_release.return_value = 'icehouse' + self.assertEquals(contexts.MongoDBContext()(), {}) + + def test_mongodb_context_related(self): + self.relation_ids.return_value = ['shared-db:0'] + self.related_units.return_value = ['mongodb/0'] + data = { + 'hostname': 'mongodb', + 'port': 8090 + } + self.test_relation.set(data) + self.assertEquals(contexts.MongoDBContext()(), + {'db_host': 'mongodb', 'db_port': 8090, + 'db_name': 'ceilometer'}) + + def test_mongodb_context_related_replset_single_mongo(self): + self.relation_ids.return_value = ['shared-db:0'] + self.related_units.return_value = ['mongodb/0'] + data = { + 'hostname': 'mongodb-0', + 'port': 8090, + 'replset': 'replset-1' + } + self.test_relation.set(data) + self.os_release.return_value = 'icehouse' + self.assertEquals(contexts.MongoDBContext()(), + {'db_host': 'mongodb-0', 'db_port': 8090, + 'db_name': 'ceilometer'}) + + @patch.object(contexts, 'context_complete') + def test_mongodb_context_related_replset_missing_values(self, mock_ctxcmp): + mock_ctxcmp.return_value = False + self.relation_ids.return_value = ['shared-db:0'] + self.related_units.return_value = ['mongodb/0'] + data = { + 'hostname': None, + 'port': 8090, + 'replset': 'replset-1' + } + self.test_relation.set(data) + self.os_release.return_value = 'icehouse' + self.assertEquals(contexts.MongoDBContext()(), {}) + + def test_mongodb_context_related_replset_multiple_mongo(self): + self.relation_ids.return_value = ['shared-db:0'] + related_units = { + 'mongodb/0': {'hostname': 'mongodb-0', + 'port': 8090, + 'replset': 'replset-1'}, + 'mongodb/1': {'hostname': 'mongodb-1', + 'port': 8090, + 'replset': 'replset-1'} + } + self.related_units.return_value = [k for k in related_units.keys()] + + def relation_get(attr, unit, relid): + values = related_units.get(unit) + if attr is None: + return values + else: + return values.get(attr, None) + self.relation_get.side_effect = relation_get + + self.os_release.return_value = 'icehouse' + self.assertEquals(contexts.MongoDBContext()(), + {'db_mongo_servers': 'mongodb-0:8090,mongodb-1:8090', + 'db_name': 'ceilometer', 'db_replset': 'replset-1'}) + + @patch.object(utils, 'get_shared_secret') + def test_ceilometer_context(self, secret): + secret.return_value = 'mysecret' + self.assertEquals(contexts.CeilometerContext()(), { + 'port': 8777, + 'metering_secret': 'mysecret', + 'api_workers': 1, + }) + + def test_ceilometer_service_context(self): + self.relation_ids.return_value = ['ceilometer-service:0'] + self.related_units.return_value = ['ceilometer/0'] + data = { + 'metering_secret': 'mysecret', + 'keystone_host': 'test' + } + self.test_relation.set(data) + self.assertEquals(contexts.CeilometerServiceContext()(), data) + + def test_ceilometer_service_context_not_related(self): + self.relation_ids.return_value = [] + self.assertEquals(contexts.CeilometerServiceContext()(), {}) + + @patch('os.path.exists') + def test_get_shared_secret_existing(self, exists): + exists.return_value = True + with mock_open(utils.SHARED_SECRET, u'mysecret'): + self.assertEquals(utils.get_shared_secret(), + 'mysecret') + + @patch('uuid.uuid4') + @patch('os.path.exists') + def test_get_shared_secret_new(self, exists, uuid4): + exists.return_value = False + uuid4.return_value = 'newsecret' + with patch('__builtin__.open'): + self.assertEquals(utils.get_shared_secret(), + 'newsecret') + + @patch.object(contexts, 'determine_apache_port') + @patch.object(contexts, 'determine_api_port') + def test_ha_proxy_context(self, determine_api_port, determine_apache_port): + determine_api_port.return_value = contexts.CEILOMETER_PORT - 10 + determine_apache_port.return_value = contexts.CEILOMETER_PORT - 20 + + haproxy_port = contexts.CEILOMETER_PORT + api_port = haproxy_port - 10 + apache_port = api_port - 10 + + expected = { + 'service_ports': {'ceilometer_api': [haproxy_port, apache_port]}, + 'port': api_port + } + self.assertEquals(contexts.HAProxyContext()(), expected) diff --git a/charms/trusty/ceilometer/unit_tests/test_ceilometer_hooks.py b/charms/trusty/ceilometer/unit_tests/test_ceilometer_hooks.py new file mode 100644 index 0000000..1a0b1b1 --- /dev/null +++ b/charms/trusty/ceilometer/unit_tests/test_ceilometer_hooks.py @@ -0,0 +1,370 @@ +import os +import sys + +from mock import patch, MagicMock, call + +# python-apt is not installed as part of test-requirements but is imported by +# some charmhelpers modules so create a fake import. +mock_apt = MagicMock() +sys.modules['apt'] = mock_apt +mock_apt.apt_pkg = MagicMock() + + +import ceilometer_utils +# Patch out register_configs for import of hooks +_register_configs = ceilometer_utils.register_configs +ceilometer_utils.register_configs = MagicMock() + +with patch('charmhelpers.contrib.hardening.harden.harden') as mock_dec: + mock_dec.side_effect = (lambda *dargs, **dkwargs: lambda f: + lambda *args, **kwargs: f(*args, **kwargs)) + import ceilometer_hooks as hooks + +# Renable old function +ceilometer_utils.register_configs = _register_configs + +from test_utils import CharmTestCase + +TO_PATCH = [ + 'relation_get', + 'relation_set', + 'configure_installation_source', + 'openstack_upgrade_available', + 'do_openstack_upgrade', + 'apt_install', + 'apt_update', + 'open_port', + 'config', + 'log', + 'relation_ids', + 'filter_installed_packages', + 'CONFIGS', + 'get_ceilometer_context', + 'lsb_release', + 'get_packages', + 'service_restart', + 'update_nrpe_config', + 'peer_retrieve', + 'peer_store', + 'configure_https', + 'status_set', +] + + +class CeilometerHooksTest(CharmTestCase): + + def setUp(self): + super(CeilometerHooksTest, self).setUp(hooks, TO_PATCH) + self.config.side_effect = self.test_config.get + self.get_packages.return_value = \ + ceilometer_utils.CEILOMETER_BASE_PACKAGES + self.filter_installed_packages.return_value = \ + ceilometer_utils.CEILOMETER_BASE_PACKAGES + self.lsb_release.return_value = {'DISTRIB_CODENAME': 'precise'} + + @patch('charmhelpers.payload.execd.default_execd_dir', + return_value=os.path.join(os.getcwd(), 'exec.d')) + @patch('charmhelpers.core.hookenv.config') + def test_configure_source(self, mock_config, mock_execd_dir): + self.test_config.set('openstack-origin', 'cloud:precise-havana') + hooks.hooks.execute(['hooks/install.real']) + self.configure_installation_source.\ + assert_called_with('cloud:precise-havana') + + @patch('charmhelpers.payload.execd.default_execd_dir', + return_value=os.path.join(os.getcwd(), 'exec.d')) + @patch('charmhelpers.core.hookenv.config') + def test_install_hook_precise(self, mock_config, mock_execd_dir): + hooks.hooks.execute(['hooks/install.real']) + self.configure_installation_source.\ + assert_called_with('cloud:precise-grizzly') + self.open_port.assert_called_with(hooks.CEILOMETER_PORT) + self.apt_update.assert_called_with(fatal=True) + self.apt_install.assert_called_with( + ceilometer_utils.CEILOMETER_BASE_PACKAGES, + fatal=True + ) + + @patch('charmhelpers.payload.execd.default_execd_dir', + return_value=os.path.join(os.getcwd(), 'exec.d')) + @patch('charmhelpers.core.hookenv.config') + def test_install_hook_distro(self, mock_config, mock_execd_dir): + self.lsb_release.return_value = {'DISTRIB_CODENAME': 'saucy'} + hooks.hooks.execute(['hooks/install.real']) + self.configure_installation_source.\ + assert_called_with('distro') + self.open_port.assert_called_with(hooks.CEILOMETER_PORT) + self.apt_update.assert_called_with(fatal=True) + self.apt_install.assert_called_with( + ceilometer_utils.CEILOMETER_BASE_PACKAGES, + fatal=True + ) + + @patch('charmhelpers.core.hookenv.config') + def test_amqp_joined(self, mock_config): + hooks.hooks.execute(['hooks/amqp-relation-joined']) + self.relation_set.assert_called_with( + username=self.test_config.get('rabbit-user'), + vhost=self.test_config.get('rabbit-vhost')) + + @patch('charmhelpers.core.hookenv.config') + def test_db_joined(self, mock_config): + hooks.hooks.execute(['hooks/shared-db-relation-joined']) + self.relation_set.assert_called_with( + ceilometer_database='ceilometer') + + @patch('charmhelpers.core.hookenv.config') + @patch.object(hooks, 'ceilometer_joined') + def test_any_changed(self, joined, mock_config): + hooks.hooks.execute(['hooks/shared-db-relation-changed']) + self.assertTrue(self.CONFIGS.write_all.called) + self.assertTrue(joined.called) + + @patch('charmhelpers.core.hookenv.config') + @patch.object(hooks, 'install') + @patch.object(hooks, 'any_changed') + def test_upgrade_charm(self, changed, install, mock_config): + hooks.hooks.execute(['hooks/upgrade-charm']) + self.assertTrue(changed.called) + self.assertTrue(install.called) + + @patch('charmhelpers.core.hookenv.config') + @patch.object(hooks, 'ceilometer_joined') + def test_config_changed_no_upgrade(self, joined, mock_config): + self.openstack_upgrade_available.return_value = False + hooks.hooks.execute(['hooks/config-changed']) + self.openstack_upgrade_available.\ + assert_called_with('ceilometer-common') + self.assertFalse(self.do_openstack_upgrade.called) + self.assertTrue(self.CONFIGS.write_all.called) + self.assertTrue(joined.called) + + @patch('charmhelpers.core.hookenv.config') + @patch.object(hooks, 'ceilometer_joined') + def test_config_changed_upgrade(self, joined, mock_config): + self.openstack_upgrade_available.return_value = True + hooks.hooks.execute(['hooks/config-changed']) + self.openstack_upgrade_available.\ + assert_called_with('ceilometer-common') + self.assertTrue(self.do_openstack_upgrade.called) + self.assertTrue(self.CONFIGS.write_all.called) + self.assertTrue(joined.called) + + def test_config_changed_with_openstack_upgrade_action(self): + self.openstack_upgrade_available.return_value = True + self.test_config.set('action-managed-upgrade', True) + + hooks.hooks.execute(['hooks/config-changed']) + + self.assertFalse(self.do_openstack_upgrade.called) + + @patch.object(hooks, 'canonical_url') + @patch('charmhelpers.core.hookenv.config') + def test_keystone_joined(self, mock_config, _canonical_url): + _canonical_url.return_value = "http://thishost" + self.test_config.set('region', 'myregion') + hooks.hooks.execute(['hooks/identity-service-relation-joined']) + url = "http://{}:{}".format('thishost', hooks.CEILOMETER_PORT) + self.relation_set.assert_called_with( + service=hooks.CEILOMETER_SERVICE, + public_url=url, admin_url=url, internal_url=url, + requested_roles=hooks.CEILOMETER_ROLE, + region='myregion', relation_id=None) + + @patch('charmhelpers.contrib.openstack.ip.service_name', + lambda *args: 'ceilometer') + @patch('charmhelpers.contrib.openstack.ip.unit_get') + @patch('charmhelpers.contrib.openstack.ip.is_clustered') + @patch('charmhelpers.core.hookenv.config') + @patch('charmhelpers.contrib.openstack.ip.config') + def test_keystone_joined_url_override(self, _config, mock_config, + _is_clustered, _unit_get): + _unit_get.return_value = "thishost" + _is_clustered.return_value = False + _config.side_effect = self.test_config.get + mock_config.side_effect = self.test_config.get + self.test_config.set('region', 'myregion') + self.test_config.set('os-public-hostname', 'ceilometer.example.com') + hooks.keystone_joined(None) + url = "http://{}:{}".format('thishost', hooks.CEILOMETER_PORT) + public_url = "http://{}:{}".format('ceilometer.example.com', + hooks.CEILOMETER_PORT) + self.relation_set.assert_called_with( + service=hooks.CEILOMETER_SERVICE, + public_url=public_url, admin_url=url, internal_url=url, + requested_roles=hooks.CEILOMETER_ROLE, + region='myregion', relation_id=None) + + @patch('charmhelpers.core.hookenv.config') + def test_ceilometer_joined(self, mock_config): + self.relation_ids.return_value = ['ceilometer:0'] + self.get_ceilometer_context.return_value = {'test': 'data'} + hooks.hooks.execute(['hooks/ceilometer-service-relation-joined']) + self.relation_set.assert_called_with('ceilometer:0', + {'test': 'data'}) + + @patch('charmhelpers.core.hookenv.config') + def test_identity_notifications_changed(self, mock_config): + self.relation_ids.return_value = ['keystone-notifications:0'] + + self.relation_get.return_value = None + hooks.hooks.execute(['hooks/identity-notifications-relation-changed']) + + self.relation_get.return_value = {('%s-endpoint-changed' % + (hooks.CEILOMETER_SERVICE)): 1} + + hooks.hooks.execute(['hooks/identity-notifications-relation-changed']) + call1 = call('ceilometer-alarm-evaluator') + call2 = call('ceilometer-alarm-notifier') + self.service_restart.assert_has_calls([call1, call2], any_order=False) + + @patch('charmhelpers.core.hookenv.config') + @patch.object(hooks, 'install_ceilometer_ocf') + @patch.object(hooks, 'is_elected_leader') + def test_cluster_joined_not_leader(self, mock_leader, mock_install_ocf, + mock_config): + mock_leader.return_value = False + + hooks.hooks.execute(['hooks/cluster-relation-joined']) + self.assertFalse(self.relation_set.called) + self.assertTrue(self.CONFIGS.write_all.called) + + @patch('charmhelpers.core.hookenv.config') + @patch.object(hooks, 'get_shared_secret') + @patch.object(hooks, 'install_ceilometer_ocf') + @patch.object(hooks, 'is_elected_leader') + def test_cluster_joined_is_leader(self, mock_leader, mock_install_ocf, + shared_secret, mock_config): + mock_leader.return_value = True + shared_secret.return_value = 'secret' + + hooks.hooks.execute(['hooks/cluster-relation-joined']) + self.assertTrue(self.peer_store.called) + self.peer_store.assert_called_with('shared_secret', 'secret') + self.assertTrue(self.CONFIGS.write_all.called) + + @patch('charmhelpers.core.hookenv.config') + @patch.object(hooks, 'set_shared_secret') + def test_cluster_changed(self, shared_secret, mock_config): + self.peer_retrieve.return_value = None + hooks.hooks.execute(['hooks/cluster-relation-changed']) + self.assertFalse(shared_secret.called) + + @patch('charmhelpers.core.hookenv.config') + @patch.object(hooks, 'get_shared_secret') + @patch.object(hooks, 'set_shared_secret') + def test_cluster_changed_new_secret(self, mock_set_secret, mock_get_secret, + mock_config): + self.peer_retrieve.return_value = "leader_secret" + mock_get_secret.return_value = "my_secret" + hooks.hooks.execute(['hooks/cluster-relation-changed']) + mock_set_secret.assert_called_with("leader_secret") + + @patch('charmhelpers.core.hookenv.config') + @patch.object(hooks, 'get_shared_secret') + @patch.object(hooks, 'set_shared_secret') + def test_cluster_changed_old_secret(self, mock_set_secret, mock_get_secret, + mock_config): + self.peer_retrieve.return_value = "leader_secret" + mock_get_secret.return_value = "leader_secret" + hooks.hooks.execute(['hooks/cluster-relation-changed']) + self.assertEquals(mock_set_secret.call_count, 0) + + @patch('charmhelpers.core.hookenv.config') + @patch.object(hooks, 'get_hacluster_config') + @patch.object(hooks, 'get_iface_for_address') + @patch.object(hooks, 'get_netmask_for_address') + def test_ha_joined(self, mock_netmask, mock_iface, mock_cluster_config, + mock_config): + mock_cluster_config.return_value = {'vip': '10.0.5.100', + 'ha-bindiface': 'bnd0', + 'ha-mcastport': 5802} + mock_iface.return_value = 'eth0' + mock_netmask.return_value = '255.255.255.10' + hooks.hooks.execute(['hooks/ha-relation-joined']) + self.assertEquals(self.relation_set.call_count, 2) + + exp_resources = { + 'res_ceilometer_haproxy': 'lsb:haproxy', + 'res_ceilometer_agent_central': ('ocf:openstack:' + 'ceilometer-agent-central'), + 'res_ceilometer_eth0_vip': 'ocf:heartbeat:IPaddr2' + } + exp_resource_params = { + 'res_ceilometer_haproxy': 'op monitor interval="5s"', + 'res_ceilometer_agent_central': 'op monitor interval="30s"', + 'res_ceilometer_eth0_vip': ('params ip="10.0.5.100" ' + 'cidr_netmask="255.255.255.10" ' + 'nic="eth0"') + } + exp_clones = {'cl_ceilometer_haproxy': 'res_ceilometer_haproxy'} + call1 = call(groups={'grp_ceilometer_vips': 'res_ceilometer_eth0_vip'}) + call2 = call(init_services={'res_ceilometer_haproxy': 'haproxy'}, + corosync_bindiface='bnd0', + corosync_mcastport=5802, + resources=exp_resources, + resource_params=exp_resource_params, + clones=exp_clones) + self.relation_set.assert_has_calls([call1, call2], any_order=False) + + @patch('charmhelpers.core.hookenv.config') + @patch.object(hooks, 'get_netmask_for_address') + @patch.object(hooks, 'get_hacluster_config') + @patch.object(hooks, 'get_iface_for_address') + @patch.object(hooks, 'relation_ids') + @patch.object(hooks, 'related_units') + @patch.object(hooks, 'relation_get') + def test_ha_joined_ssl(self, mock_rel_get, mock_rel_units, mock_rel_ids, + mock_iface, mock_cluster_config, mock_netmask, + mock_config): + mock_rel_ids.return_value = 'amqp:0' + mock_rel_units.return_value = 'rabbitmq-server/0' + mock_rel_get.return_value = '5671' + + mock_iface.return_value = 'eth0' + mock_netmask.return_value = '255.255.255.10' + mock_cluster_config.return_value = {'vip': '10.0.5.100', + 'ha-bindiface': 'bnd0', + 'ha-mcastport': 5802} + + hooks.hooks.execute(['hooks/ha-relation-joined']) + self.assertEquals(self.relation_set.call_count, 2) + + exp_resources = { + 'res_ceilometer_haproxy': 'lsb:haproxy', + 'res_ceilometer_agent_central': ('ocf:openstack:' + 'ceilometer-agent-central'), + 'res_ceilometer_eth0_vip': 'ocf:heartbeat:IPaddr2' + } + exp_resource_params = { + 'res_ceilometer_haproxy': 'op monitor interval="5s"', + 'res_ceilometer_agent_central': ('params amqp_server_port="5671" ' + 'op monitor interval="30s"'), + 'res_ceilometer_eth0_vip': ('params ip="10.0.5.100" ' + 'cidr_netmask="255.255.255.10" ' + 'nic="eth0"') + } + exp_clones = {'cl_ceilometer_haproxy': 'res_ceilometer_haproxy'} + call1 = call(groups={'grp_ceilometer_vips': 'res_ceilometer_eth0_vip'}) + call2 = call(init_services={'res_ceilometer_haproxy': 'haproxy'}, + corosync_bindiface='bnd0', + corosync_mcastport=5802, + resources=exp_resources, + resource_params=exp_resource_params, + clones=exp_clones) + self.relation_set.assert_has_calls([call1, call2], any_order=False) + + @patch('charmhelpers.core.hookenv.config') + @patch.object(hooks, 'keystone_joined') + def test_ha_changed_not_clustered(self, mock_keystone_joined, mock_config): + self.relation_get.return_value = None + hooks.hooks.execute(['hooks/ha-relation-changed']) + self.assertEquals(mock_keystone_joined.call_count, 0) + + @patch('charmhelpers.core.hookenv.config') + @patch.object(hooks, 'keystone_joined') + def test_ha_changed_clustered(self, mock_keystone_joined, mock_config): + self.relation_get.return_value = 'yes' + self.relation_ids.return_value = ['identity-service/0'] + hooks.hooks.execute(['hooks/ha-relation-changed']) + self.assertEquals(mock_keystone_joined.call_count, 1) diff --git a/charms/trusty/ceilometer/unit_tests/test_ceilometer_utils.py b/charms/trusty/ceilometer/unit_tests/test_ceilometer_utils.py new file mode 100644 index 0000000..8bfe59e --- /dev/null +++ b/charms/trusty/ceilometer/unit_tests/test_ceilometer_utils.py @@ -0,0 +1,180 @@ +from mock import patch, call, MagicMock + +import ceilometer_utils as utils + +from test_utils import CharmTestCase + +TO_PATCH = [ + 'get_os_codename_package', + 'get_os_codename_install_source', + 'configure_installation_source', + 'templating', + 'LoggingConfigContext', + 'MongoDBContext', + 'CeilometerContext', + 'config', + 'log', + 'apt_install', + 'apt_update', + 'apt_upgrade', +] + + +class CeilometerUtilsTest(CharmTestCase): + + def setUp(self): + super(CeilometerUtilsTest, self).setUp(utils, TO_PATCH) + self.config.side_effect = self.test_config.get + + def tearDown(self): + super(CeilometerUtilsTest, self).tearDown() + + def test_register_configs(self): + configs = utils.register_configs() + calls = [] + for conf in utils.CONFIG_FILES: + calls.append(call(conf, + utils.CONFIG_FILES[conf]['hook_contexts'])) + configs.register.assert_has_calls(calls, any_order=True) + + def test_ceilometer_release_services(self): + """Ensure that icehouse specific services are identified""" + self.get_os_codename_install_source.return_value = 'icehouse' + self.assertEqual(['ceilometer-alarm-notifier', + 'ceilometer-alarm-evaluator', + 'ceilometer-agent-notification'], + utils.ceilometer_release_services()) + + def test_ceilometer_release_services_mitaka(self): + """Ensure that mitaka specific services are identified""" + self.get_os_codename_install_source.return_value = 'mitaka' + self.assertEqual(['ceilometer-agent-notification'], + utils.ceilometer_release_services()) + + def test_restart_map(self): + """Ensure that alarming services are present for < OpenStack Mitaka""" + self.get_os_codename_install_source.return_value = 'icehouse' + restart_map = utils.restart_map() + self.assertEquals( + restart_map, + {'/etc/ceilometer/ceilometer.conf': [ + 'ceilometer-agent-central', + 'ceilometer-collector', + 'ceilometer-api', + 'ceilometer-alarm-notifier', + 'ceilometer-alarm-evaluator', + 'ceilometer-agent-notification'], + '/etc/haproxy/haproxy.cfg': ['haproxy'], + "/etc/apache2/sites-available/openstack_https_frontend": [ + 'apache2'], + "/etc/apache2/sites-available/openstack_https_frontend.conf": [ + 'apache2'] + } + ) + + def test_restart_map_mitaka(self): + """Ensure that alarming services are missing for OpenStack Mitaka""" + self.get_os_codename_install_source.return_value = 'mitaka' + restart_map = utils.restart_map() + self.assertEquals( + restart_map, + {'/etc/ceilometer/ceilometer.conf': [ + 'ceilometer-agent-central', + 'ceilometer-collector', + 'ceilometer-api', + 'ceilometer-agent-notification'], + '/etc/haproxy/haproxy.cfg': ['haproxy'], + "/etc/apache2/sites-available/openstack_https_frontend": [ + 'apache2'], + "/etc/apache2/sites-available/openstack_https_frontend.conf": [ + 'apache2'] + } + ) + + def test_get_ceilometer_conf(self): + class TestContext(): + + def __call__(self): + return {'data': 'test'} + with patch.dict(utils.CONFIG_FILES, + {'/etc/ceilometer/ceilometer.conf': { + 'hook_contexts': [TestContext()] + }}): + self.assertTrue(utils.get_ceilometer_context(), + {'data': 'test'}) + + def test_do_openstack_upgrade(self): + self.config.side_effect = self.test_config.get + self.test_config.set('openstack-origin', 'cloud:trusty-kilo') + self.get_os_codename_install_source.return_value = 'kilo' + configs = MagicMock() + utils.do_openstack_upgrade(configs) + configs.set_release.assert_called_with(openstack_release='kilo') + self.assertTrue(self.log.called) + self.apt_update.assert_called_with(fatal=True) + dpkg_opts = [ + '--option', 'Dpkg::Options::=--force-confnew', + '--option', 'Dpkg::Options::=--force-confdef', + ] + self.apt_install.assert_called_with( + packages=utils.CEILOMETER_BASE_PACKAGES + utils.ICEHOUSE_PACKAGES, + options=dpkg_opts, fatal=True + ) + self.configure_installation_source.assert_called_with( + 'cloud:trusty-kilo' + ) + + def test_get_packages_icehouse(self): + self.get_os_codename_install_source.return_value = 'icehouse' + self.assertEqual(utils.get_packages(), + utils.CEILOMETER_BASE_PACKAGES + + utils.ICEHOUSE_PACKAGES) + + def test_get_packages_mitaka(self): + self.get_os_codename_install_source.return_value = 'mitaka' + self.assertEqual(utils.get_packages(), + utils.CEILOMETER_BASE_PACKAGES + + utils.MITAKA_PACKAGES) + + def test_assess_status(self): + with patch.object(utils, 'assess_status_func') as asf: + callee = MagicMock() + asf.return_value = callee + utils.assess_status('test-config') + asf.assert_called_once_with('test-config') + callee.assert_called_once_with() + + @patch.object(utils, 'REQUIRED_INTERFACES') + @patch.object(utils, 'services') + @patch.object(utils, 'determine_ports') + @patch.object(utils, 'make_assess_status_func') + def test_assess_status_func(self, + make_assess_status_func, + determine_ports, + services, + REQUIRED_INTERFACES): + services.return_value = 's1' + determine_ports.return_value = 'p1' + utils.assess_status_func('test-config') + make_assess_status_func.assert_called_once_with( + 'test-config', REQUIRED_INTERFACES, services='s1', ports='p1') + + def test_pause_unit_helper(self): + with patch.object(utils, '_pause_resume_helper') as prh: + utils.pause_unit_helper('random-config') + prh.assert_called_once_with(utils.pause_unit, 'random-config') + with patch.object(utils, '_pause_resume_helper') as prh: + utils.resume_unit_helper('random-config') + prh.assert_called_once_with(utils.resume_unit, 'random-config') + + @patch.object(utils, 'services') + @patch.object(utils, 'determine_ports') + def test_pause_resume_helper(self, determine_ports, services): + f = MagicMock() + services.return_value = 's1' + determine_ports.return_value = 'p1' + with patch.object(utils, 'assess_status_func') as asf: + asf.return_value = 'assessor' + utils._pause_resume_helper(f, 'some-config') + asf.assert_called_once_with('some-config') + f.assert_called_once_with('assessor', services='s1', ports='p1') diff --git a/charms/trusty/ceilometer/unit_tests/test_utils.py b/charms/trusty/ceilometer/unit_tests/test_utils.py new file mode 100644 index 0000000..e90679e --- /dev/null +++ b/charms/trusty/ceilometer/unit_tests/test_utils.py @@ -0,0 +1,111 @@ +import logging +import unittest +import os +import yaml +import io + +from contextlib import contextmanager +from mock import patch + + +@contextmanager +def mock_open(filename, contents=None): + ''' Slightly simpler mock of open to return contents for filename ''' + def mock_file(*args): + if args[0] == filename: + return io.StringIO(contents) + else: + return open(*args) + with patch('__builtin__.open', mock_file): + yield + + +def load_config(): + ''' + Walk backwords from __file__ looking for config.yaml, load and return the + 'options' section' + ''' + config = None + f = __file__ + while config is None: + d = os.path.dirname(f) + if os.path.isfile(os.path.join(d, 'config.yaml')): + config = os.path.join(d, 'config.yaml') + break + f = d + + if not config: + logging.error('Could not find config.yaml in any parent directory ' + 'of %s. ' % file) + raise Exception + + return yaml.safe_load(open(config).read())['options'] + + +def get_default_config(): + ''' + Load default charm config from config.yaml return as a dict. + If no default is set in config.yaml, its value is None. + ''' + default_config = {} + config = load_config() + for k, v in config.iteritems(): + if 'default' in v: + default_config[k] = v['default'] + else: + default_config[k] = None + return default_config + + +class CharmTestCase(unittest.TestCase): + def setUp(self, obj, patches): + super(CharmTestCase, self).setUp() + self.patches = patches + self.obj = obj + self.test_config = TestConfig() + self.test_relation = TestRelation() + 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 TestConfig(object): + def __init__(self): + self.config = get_default_config() + + def get(self, attr): + try: + return self.config[attr] + except KeyError: + return None + + def get_all(self): + return self.config + + def set(self, attr, value): + if attr not in self.config: + raise KeyError + self.config[attr] = value + + +class TestRelation(object): + def __init__(self, relation_data={}): + self.relation_data = relation_data + + def set(self, relation_data): + self.relation_data = relation_data + + def get(self, attr=None, unit=None, rid=None): + if attr is None: + return self.relation_data + elif attr in self.relation_data: + return self.relation_data[attr] + return None |