charm-heat/unit_tests/test_heat_relations.py

395 lines
16 KiB
Python

# Copyright 2016 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 os.path
import sys
from unittest.mock import call, patch, MagicMock
from test_utils import CharmTestCase, patch_open
# 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 heat_utils as utils
_reg = utils.register_configs
_map = utils.restart_map
utils.register_configs = MagicMock()
utils.restart_map = 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 heat_relations as relations
utils.register_configs = _reg
utils.restart_map = _map
TO_PATCH = [
# charmhelpers.core.hookenv
'Hooks',
'config',
'is_leader',
'leader_set',
'log',
'open_port',
'relation_set',
'related_units',
# charmhelpers.core.host
'apt_install',
'apt_update',
'restart_on_change',
'service_restart',
# charmhelpers.contrib.openstack.utils
'charm_dir',
'configure_installation_source',
'determine_packages',
'openstack_upgrade_available',
'os_release',
'sync_db_with_multi_ipv6_addresses',
# charmhelpers.contrib.openstack.ha.utils
'generate_ha_relation_data',
# charmhelpers.contrib.hahelpers.cluster_utils
# heat_utils
'restart_map',
'register_configs',
'do_openstack_upgrade',
'remove_old_packages',
'services',
# other
'execd_preinstall',
'log',
'migrate_database',
'is_elected_leader',
'relation_ids',
'relation_get',
'local_unit',
'get_relation_ip',
'is_db_maintenance_mode',
]
class HeatRelationTests(CharmTestCase):
def setUp(self):
super(HeatRelationTests, self).setUp(relations, TO_PATCH)
self.config.side_effect = self.test_config.get
self.charm_dir.return_value = '/var/lib/juju/charms/heat/charm'
@patch.object(relations.policyd, 'maybe_do_policyd_overrides')
def test_install_hook(self, mock_maybe_do_policyd_overrides):
repo = 'cloud:precise-havana'
self.determine_packages.return_value = [
'python-keystoneclient', 'uuid', 'heat-api',
'heat-api-cfn', 'heat-engine']
self.test_config.set('openstack-origin', repo)
relations.install()
self.configure_installation_source.assert_called_with(repo)
self.assertTrue(self.apt_update.called)
self.apt_install.assert_called_with(['python-keystoneclient',
'uuid', 'heat-api',
'heat-api-cfn',
'heat-engine'], fatal=True)
self.assertTrue(self.execd_preinstall.called)
@patch.object(relations, 'configure_https')
@patch.object(relations.policyd,
'maybe_do_policyd_overrides_on_config_changed')
@patch.object(relations, 'update_nrpe_config')
def test_config_changed_no_upgrade(
self,
mock_update_nrpe_config,
maybe_do_policyd_overrides_on_config_changed,
mock_configure_https,
):
self.openstack_upgrade_available.return_value = False
relations.config_changed()
@patch.object(relations, 'configure_https')
@patch.object(relations.policyd,
'maybe_do_policyd_overrides_on_config_changed')
@patch.object(relations, 'update_nrpe_config')
def test_config_changed_with_upgrade(
self,
mock_update_nrpe_config,
maybe_do_policyd_overrides_on_config_changed,
mock_configure_https
):
self.openstack_upgrade_available.return_value = True
relations.config_changed()
self.assertTrue(self.do_openstack_upgrade.called)
@patch.object(relations, 'configure_https')
@patch.object(relations.policyd,
'maybe_do_policyd_overrides_on_config_changed')
@patch.object(relations, 'update_nrpe_config')
def test_config_changed_with_openstack_upgrade_action(
self,
mock_update_nrpe_config,
maybe_do_policyd_overrides_on_config_changed,
mock_configure_https
):
self.openstack_upgrade_available.return_value = True
self.test_config.set('action-managed-upgrade', True)
relations.config_changed()
self.assertFalse(self.do_openstack_upgrade.called)
@patch('os.path.isfile')
@patch('os.remove')
@patch.object(relations, 'leader_elected')
@patch.object(relations.policyd, 'maybe_do_policyd_overrides')
@patch.object(relations, 'update_nrpe_config')
def test_upgrade_charm(self,
mock_update_nrpe_config,
mock_maybe_do_policyd_overrides,
leader_elected, os_remove, os_path_isfile):
os_path_isfile.return_value = False
self.is_leader.return_value = False
self.remove_old_packages.return_value = True
self.services.return_value = ['heat-api']
relations.upgrade_charm()
leader_elected.assert_called_once_with()
os_path_isfile.assert_not_called()
# now say we are the leader
self.is_leader.return_value = True
os_path_isfile.return_value = False
relations.upgrade_charm()
self.leader_set.assert_not_called()
os_path_isfile.return_value = True
with patch_open() as (mock_open, mock_file):
mock_file.read.return_value = "abc"
relations.upgrade_charm()
filename = os.path.join(relations.HEAT_PATH, 'encryption-key')
mock_open.assert_called_once_with(filename, 'r')
self.leader_set.assert_called_once_with(
{'heat-auth-encryption-key': 'abc'})
os_remove.assert_called_once_with(filename)
self.remove_old_packages.assert_called_with()
self.service_restart.assert_called_with('heat-api')
def test_db_joined(self):
self.get_relation_ip.return_value = '192.168.20.1'
relations.db_joined()
self.relation_set.assert_called_with(heat_database='heat',
heat_username='heat',
heat_hostname='192.168.20.1')
self.assertTrue(self.get_relation_ip.called)
def _shared_db_test(self, configs):
self.is_db_maintenance_mode.return_value = False
self.relation_get.return_value = 'heat/0 heat/1'
self.local_unit.return_value = 'heat/0'
configs.complete_contexts = MagicMock()
configs.complete_contexts.return_value = ['shared-db']
configs.write = MagicMock()
relations.db_changed()
self.assertTrue(self.migrate_database.called)
@patch.object(relations, 'CONFIGS')
def test_db_changed(self, configs):
self._shared_db_test(configs)
self.assertEqual([call('/etc/heat/heat.conf')],
configs.write.call_args_list)
@patch.object(relations, 'CONFIGS')
def test_db_changed_missing_relation_data(self, configs):
self.is_db_maintenance_mode.return_value = False
configs.complete_contexts = MagicMock()
configs.complete_contexts.return_value = []
relations.db_changed()
self.log.assert_called_with(
'shared-db relation incomplete. Peer not ready?'
)
def test_amqp_joined(self):
relations.amqp_joined()
self.relation_set.assert_called_with(
username='heat',
vhost='openstack',
relation_id=None,
ttlname='heat_expiry',
ttlreg='heat-engine-listener|engine_worker',
ttl=3600000)
def test_amqp_joined_passes_relation_id(self):
"Ensures relation_id correct passed to relation_set"
relations.amqp_joined(relation_id='heat:1')
self.relation_set.assert_called_with(
username='heat', vhost='openstack', relation_id='heat:1',
ttlname='heat_expiry',
ttlreg='heat-engine-listener|engine_worker', ttl=3600000)
@patch.object(relations, 'CONFIGS')
def test_amqp_changed_relation_data(self, configs):
configs.complete_contexts = MagicMock()
configs.complete_contexts.return_value = ['amqp']
configs.write = MagicMock()
relations.amqp_changed()
self.assertEqual([call('/etc/heat/heat.conf')],
configs.write.call_args_list)
@patch.object(relations, 'CONFIGS')
def test_amqp_changed_missing_relation_data(self, configs):
configs.complete_contexts = MagicMock()
configs.complete_contexts.return_value = []
relations.amqp_changed()
self.assertTrue(self.log.called)
@patch.object(relations, 'CONFIGS')
def test_relation_broken(self, configs):
relations.relation_broken()
self.assertTrue(configs.write_all.called)
@patch.object(relations, 'canonical_url')
def test_identity_service_joined(self, _canonical_url):
"It properly requests unclustered endpoint via identity-service"
_canonical_url.return_value = 'http://heatnode1'
relations.identity_joined()
expected = {
'heat_service': 'heat',
'heat_region': 'RegionOne',
'heat_public_url': 'http://heatnode1:8004/v1/$(tenant_id)s',
'heat_admin_url': 'http://heatnode1:8004/v1/$(tenant_id)s',
'heat_internal_url': 'http://heatnode1:8004/v1/$(tenant_id)s',
'heat-cfn_service': 'heat-cfn',
'heat-cfn_region': 'RegionOne',
'heat-cfn_public_url': 'http://heatnode1:8000/v1',
'heat-cfn_admin_url': 'http://heatnode1:8000/v1',
'heat-cfn_internal_url': 'http://heatnode1:8000/v1',
'relation_id': None,
}
self.relation_set.assert_called_with(**expected)
@patch.object(relations, 'canonical_url')
def test_identity_service_joined_with_relation_id(self, _canonical_url):
_canonical_url.return_value = 'http://heatnode1'
relations.identity_joined(rid='identity-service:0')
ex = {
'heat_service': 'heat',
'heat_region': 'RegionOne',
'heat_public_url': 'http://heatnode1:8004/v1/$(tenant_id)s',
'heat_admin_url': 'http://heatnode1:8004/v1/$(tenant_id)s',
'heat_internal_url': 'http://heatnode1:8004/v1/$(tenant_id)s',
'heat-cfn_service': 'heat-cfn',
'heat-cfn_region': 'RegionOne',
'heat-cfn_public_url': 'http://heatnode1:8000/v1',
'heat-cfn_admin_url': 'http://heatnode1:8000/v1',
'heat-cfn_internal_url': 'http://heatnode1:8000/v1',
'relation_id': 'identity-service:0',
}
self.relation_set.assert_called_with(**ex)
@patch('charmhelpers.contrib.openstack.ip.unit_get',
lambda *args: 'heatnode1')
@patch('charmhelpers.contrib.openstack.ip.is_clustered',
lambda *args: False)
@patch('charmhelpers.contrib.openstack.ip.service_name')
@patch('charmhelpers.contrib.openstack.ip.config')
def test_identity_service_public_address_override(self, ip_config,
_service_name):
ip_config.side_effect = self.test_config.get
_service_name.return_value = 'heat'
self.test_config.set('os-public-hostname', 'heat.example.org')
relations.identity_joined(rid='identity-service:0')
exp = {
'heat_service': 'heat',
'heat_region': 'RegionOne',
'heat_public_url': 'http://heat.example.org:8004/v1/$(tenant_id)s',
'heat_admin_url': 'http://heatnode1:8004/v1/$(tenant_id)s',
'heat_internal_url': 'http://heatnode1:8004/v1/$(tenant_id)s',
'heat-cfn_service': 'heat-cfn',
'heat-cfn_region': 'RegionOne',
'heat-cfn_public_url': 'http://heat.example.org:8000/v1',
'heat-cfn_admin_url': 'http://heatnode1:8000/v1',
'heat-cfn_internal_url': 'http://heatnode1:8000/v1',
'relation_id': 'identity-service:0',
}
self.relation_set.assert_called_with(**exp)
@patch.object(relations, 'configure_https')
@patch.object(relations, 'CONFIGS')
def test_identity_changed(self, configs, mock_configure_https):
configs.complete_contexts.return_value = ['identity-service']
relations.identity_changed()
self.assertTrue(configs.write_all.called)
@patch.object(relations, 'CONFIGS')
def test_identity_changed_incomplete(self, configs):
configs.complete_contexts.return_value = []
relations.identity_changed()
self.assertTrue(self.log.called)
self.assertFalse(configs.write.called)
def test_db_joined_with_ipv6(self):
'It properly requests access to a shared-db service'
self.sync_db_with_multi_ipv6_addresses.return_value = MagicMock()
self.test_config.set('prefer-ipv6', True)
relations.db_joined()
self.sync_db_with_multi_ipv6_addresses.assert_called_with(
'heat', 'heat', relation_prefix='heat')
@patch.object(relations, 'CONFIGS')
def test_non_leader_db_changed(self, configs):
self.is_elected_leader.return_value = False
configs.complete_contexts.return_value = []
self.relation_get.return_value = 'heat/0 heat/1'
self.local_unit.return_value = 'heat/0'
configs.complete_contexts = MagicMock()
configs.complete_contexts.return_value = ['shared-db']
configs.write = MagicMock()
relations.db_changed()
self.assertFalse(self.migrate_database.called)
def test_ha_relation_joined(self):
self.generate_ha_relation_data.return_value = {'rel_data': 'data'}
relations.ha_joined(relation_id='rid:23')
self.relation_set.assert_called_once_with(
relation_id='rid:23', rel_data='data')
@patch.object(relations, "apt_install")
@patch.object(relations.nrpe, "get_nagios_hostname")
@patch.object(relations.nrpe, "get_nagios_unit_name")
@patch.object(relations.nrpe, "NRPE")
@patch.object(relations.nrpe, "copy_nrpe_checks")
@patch.object(relations.nrpe, "add_init_service_checks")
@patch.object(relations.nrpe, "add_haproxy_checks")
def test_update_nrpe_config(self, mock_add_haproxy, mock_add_init_svc,
mock_copy_nrpe_checks, mock_nrpe,
mock_nagios_unitname, mock_nagios_hostname,
mock_apt_install):
relations.update_nrpe_config()
mock_apt_install.assert_called_once_with("python-dbus")
mock_nagios_hostname.assert_called_once_with()
mock_nagios_unitname.assert_called_once_with()
mock_nrpe.assert_called_once_with(
hostname=mock_nagios_hostname.return_value)
mock_copy_nrpe_checks.assert_called_once_with()
mock_add_init_svc.assert_called_once_with(
mock_nrpe.return_value,
relations.services(),
mock_nagios_unitname.return_value,
)
mock_add_haproxy.assert_called_once_with(
mock_nrpe.return_value, mock_nagios_unitname.return_value)
mock_nrpe.return_value.write.assert_called_once_with()