charm-nova-compute/unit_tests/test_nova_compute_hooks.py
Liam Young 68685d59df Send remote restart to neutron plugin post upgrade
When this principle charm performs and Openstack upgrade the
subordinates packages are also upgraded. The subordinate may need
to render new config for the new release so the charm now sends a
restart trigger to the subordinate

Change-Id: Ifd5dea4e67d09dc29bb0bba579fd8903fb64490a
Partial-Bug: 1571634
2016-04-18 17:35:08 +00:00

550 lines
21 KiB
Python

import sys
import yaml
from mock import (
call,
patch,
MagicMock
)
# python-apt is not installed as part of test-requirements but is imported by
# some charmhelpers modules so create a fake import.
sys.modules['apt'] = MagicMock()
sys.modules['apt_pkg'] = MagicMock()
from test_utils import CharmTestCase
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("nova_compute_utils.restart_map"):
with patch("nova_compute_utils.register_configs"):
import nova_compute_hooks as hooks
TO_PATCH = [
# charmhelpers.core.hookenv
'Hooks',
'config',
'log',
'is_relation_made',
'relation_get',
'relation_ids',
'relation_set',
'service_name',
'unit_get',
# charmhelpers.core.host
'apt_install',
'apt_purge',
'apt_update',
'filter_installed_packages',
'restart_on_change',
'service_restart',
# charmhelpers.contrib.openstack.utils
'configure_installation_source',
'openstack_upgrade_available',
# nova_compute_utils
# 'PACKAGES',
'create_libvirt_secret',
'restart_map',
'determine_packages',
'import_authorized_keys',
'import_keystone_ca_cert',
'initialize_ssh_keys',
'migration_enabled',
'do_openstack_upgrade',
'public_ssh_key',
'register_configs',
'disable_shell',
'enable_shell',
'update_nrpe_config',
'git_install',
'git_install_requested',
# misc_utils
'ensure_ceph_keyring',
'execd_preinstall',
'assert_libvirt_imagebackend_allowed',
'is_request_complete',
'send_request_if_needed',
# socket
'gethostname',
'create_sysctl',
'install_hugepages',
'uuid',
]
class NovaComputeRelationsTests(CharmTestCase):
def setUp(self):
super(NovaComputeRelationsTests, self).setUp(hooks,
TO_PATCH)
self.config.side_effect = self.test_config.get
self.filter_installed_packages.side_effect = \
MagicMock(side_effect=lambda pkgs: pkgs)
self.gethostname.return_value = 'testserver'
def test_install_hook(self):
repo = 'cloud:precise-grizzly'
self.test_config.set('openstack-origin', repo)
self.determine_packages.return_value = ['foo', 'bar']
hooks.install()
self.configure_installation_source.assert_called_with(repo)
self.assertTrue(self.apt_update.called)
self.apt_install.assert_called_with(['foo', 'bar'], fatal=True)
self.assertTrue(self.execd_preinstall.called)
def test_install_hook_git(self):
self.git_install_requested.return_value = True
self.determine_packages.return_value = ['foo', 'bar']
repo = 'cloud:trusty-juno'
openstack_origin_git = {
'repositories': [
{'name': 'requirements',
'repository': 'git://git.openstack.org/openstack/requirements', # noqa
'branch': 'stable/juno'},
{'name': 'nova',
'repository': 'git://git.openstack.org/openstack/nova',
'branch': 'stable/juno'}
],
'directory': '/mnt/openstack-git',
}
projects_yaml = yaml.dump(openstack_origin_git)
self.test_config.set('openstack-origin', repo)
self.test_config.set('openstack-origin-git', projects_yaml)
hooks.install()
self.configure_installation_source.assert_called_with(repo)
self.assertTrue(self.apt_update.called)
self.apt_install.assert_called_with(['foo', 'bar'], fatal=True)
self.git_install.assert_called_with(projects_yaml)
self.assertTrue(self.execd_preinstall.called)
@patch.object(hooks, 'neutron_plugin_joined')
def test_config_changed_with_upgrade(self, neutron_plugin_joined):
self.git_install_requested.return_value = False
self.openstack_upgrade_available.return_value = True
def rel_ids(x):
return {'neutron-plugin': ['rid1']}.get(x, [])
self.relation_ids.side_effect = rel_ids
hooks.config_changed()
self.assertTrue(self.do_openstack_upgrade.called)
neutron_plugin_joined.assert_called_with('rid1', remote_restart=True)
@patch.object(hooks, 'git_install_requested')
def test_config_changed_with_openstack_upgrade_action(self, git_requested):
git_requested.return_value = False
self.openstack_upgrade_available.return_value = True
self.test_config.set('action-managed-upgrade', True)
hooks.config_changed()
self.assertFalse(self.do_openstack_upgrade.called)
@patch.object(hooks, 'neutron_plugin_joined')
@patch.object(hooks, 'compute_joined')
def test_config_changed_with_migration(self, compute_joined,
neutron_plugin_joined):
self.git_install_requested.return_value = False
self.migration_enabled.return_value = True
_zmq_joined = self.patch('zeromq_configuration_relation_joined')
self.test_config.set('migration-auth-type', 'ssh')
self.relation_ids.return_value = [
'cloud-compute:0',
'cloud-compute:1'
]
hooks.config_changed()
ex = [
call('cloud-compute:0'),
call('cloud-compute:1'),
]
self.assertEquals(ex, compute_joined.call_args_list)
self.assertTrue(self.initialize_ssh_keys.called)
self.assertTrue(_zmq_joined.called)
@patch.object(hooks, 'neutron_plugin_joined')
@patch.object(hooks, 'compute_joined')
def test_config_changed_with_resize(self, compute_joined,
neutron_plugin_joined):
self.git_install_requested.return_value = False
self.test_config.set('enable-resize', True)
_zmq_joined = self.patch('zeromq_configuration_relation_joined')
self.relation_ids.return_value = [
'cloud-compute:0',
'cloud-compute:1'
]
hooks.config_changed()
ex = [
call('cloud-compute:0'),
call('cloud-compute:1'),
]
self.assertEquals(ex, compute_joined.call_args_list)
self.initialize_ssh_keys.assert_called_with(user='nova')
self.enable_shell.assert_called_with(user='nova')
self.assertTrue(_zmq_joined.called)
@patch.object(hooks, 'neutron_plugin_joined')
@patch.object(hooks, 'compute_joined')
def test_config_changed_without_resize(self, compute_joined,
neutron_plugin_joined):
self.git_install_requested.return_value = False
self.test_config.set('enable-resize', False)
_zmq_joined = self.patch('zeromq_configuration_relation_joined')
self.relation_ids.return_value = [
'cloud-compute:0',
'cloud-compute:1'
]
hooks.config_changed()
ex = [
call('cloud-compute:0'),
call('cloud-compute:1'),
]
self.assertEquals(ex, compute_joined.call_args_list)
self.disable_shell.assert_called_with(user='nova')
self.assertTrue(_zmq_joined.called)
@patch.object(hooks, 'compute_joined')
def test_config_changed_no_upgrade_no_migration(self, compute_joined):
self.git_install_requested.return_value = False
self.openstack_upgrade_available.return_value = False
self.migration_enabled.return_value = False
hooks.config_changed()
self.assertFalse(self.do_openstack_upgrade.called)
self.assertFalse(compute_joined.called)
@patch.object(hooks, 'compute_joined')
def test_config_changed_with_sysctl(self, compute_joined):
self.git_install_requested.return_value = False
self.test_config.set('sysctl', '{ kernel.max_pid : "1337" }')
hooks.config_changed()
self.assertTrue(self.create_sysctl.called)
@patch.object(hooks, 'config_value_changed')
def test_config_changed_git(self, config_val_changed):
self.git_install_requested.return_value = True
repo = 'cloud:trusty-juno'
openstack_origin_git = {
'repositories': [
{'name': 'requirements',
'repository':
'git://git.openstack.org/openstack/requirements',
'branch': 'stable/juno'},
{'name': 'nova',
'repository': 'git://git.openstack.org/openstack/nova',
'branch': 'stable/juno'}
],
'directory': '/mnt/openstack-git',
}
projects_yaml = yaml.dump(openstack_origin_git)
self.test_config.set('openstack-origin', repo)
self.test_config.set('openstack-origin-git', projects_yaml)
hooks.config_changed()
self.git_install.assert_called_with(projects_yaml)
self.assertFalse(self.do_openstack_upgrade.called)
@patch.object(hooks, 'compute_joined')
def test_config_changed_no_nrpe(self, compute_joined):
self.git_install_requested.return_value = False
self.openstack_upgrade_available.return_value = False
self.migration_enabled.return_value = False
self.is_relation_made.return_value = False
hooks.config_changed()
self.assertFalse(self.update_nrpe_config.called)
@patch.object(hooks, 'compute_joined')
def test_config_changed_nrpe(self, compute_joined):
self.git_install_requested.return_value = False
self.openstack_upgrade_available.return_value = False
self.migration_enabled.return_value = False
self.is_relation_made.return_value = True
hooks.config_changed()
self.assertTrue(self.update_nrpe_config.called)
def test_amqp_joined(self):
hooks.amqp_joined()
self.relation_set.assert_called_with(
username='nova', vhost='openstack',
relation_id=None)
@patch.object(hooks, 'CONFIGS')
def test_amqp_changed_missing_relation_data(self, configs):
configs.complete_contexts = MagicMock()
configs.complete_contexts.return_value = []
hooks.amqp_changed()
self.log.assert_called_with(
'amqp relation incomplete. Peer not ready?'
)
def _amqp_test(self, configs, neutron=False):
configs.complete_contexts = MagicMock()
configs.complete_contexts.return_value = ['amqp']
configs.write = MagicMock()
hooks.amqp_changed()
@patch.object(hooks, 'CONFIGS')
def test_amqp_changed_with_data_no_neutron(self, configs):
self._amqp_test(configs)
self.assertEquals([call('/etc/nova/nova.conf')],
configs.write.call_args_list)
def test_db_joined(self):
self.unit_get.return_value = 'nova.foohost.com'
self.is_relation_made.return_value = False
hooks.db_joined()
self.relation_set.assert_called_with(relation_id=None,
nova_database='nova',
nova_username='nova',
nova_hostname='nova.foohost.com')
self.unit_get.assert_called_with('private-address')
def test_postgresql_db_joined(self):
self.unit_get.return_value = 'nova.foohost.com'
self.is_relation_made.return_value = False
hooks.pgsql_db_joined()
self.relation_set.assert_called_with(database='nova'),
def test_db_joined_with_postgresql(self):
self.is_relation_made.return_value = True
with self.assertRaises(Exception) as context:
hooks.db_joined()
self.assertEqual(context.exception.message,
'Attempting to associate a mysql database when there '
'is already associated a postgresql one')
def test_postgresql_joined_with_db(self):
self.is_relation_made.return_value = True
with self.assertRaises(Exception) as context:
hooks.pgsql_db_joined()
self.assertEqual(context.exception.message,
'Attempting to associate a postgresql database when'
' there is already associated a mysql one')
@patch.object(hooks, 'CONFIGS')
def test_db_changed_missing_relation_data(self, configs):
configs.complete_contexts = MagicMock()
configs.complete_contexts.return_value = []
hooks.db_changed()
self.log.assert_called_with(
'shared-db relation incomplete. Peer not ready?'
)
@patch.object(hooks, 'CONFIGS')
def test_postgresql_db_changed_missing_relation_data(self, configs):
configs.complete_contexts = MagicMock()
configs.complete_contexts.return_value = []
hooks.postgresql_db_changed()
self.log.assert_called_with(
'pgsql-db relation incomplete. Peer not ready?'
)
def _shared_db_test(self, configs):
configs.complete_contexts = MagicMock()
configs.complete_contexts.return_value = ['shared-db']
configs.write = MagicMock()
hooks.db_changed()
def _postgresql_db_test(self, configs):
configs.complete_contexts = MagicMock()
configs.complete_contexts.return_value = ['pgsql-db']
configs.write = MagicMock()
hooks.postgresql_db_changed()
@patch.object(hooks, 'CONFIGS')
def test_db_changed_with_data(self, configs):
self._shared_db_test(configs)
self.assertEquals([call('/etc/nova/nova.conf')],
configs.write.call_args_list)
@patch.object(hooks, 'CONFIGS')
def test_postgresql_db_changed_with_data(self, configs):
self._postgresql_db_test(configs)
self.assertEquals([call('/etc/nova/nova.conf')],
configs.write.call_args_list)
@patch.object(hooks, 'CONFIGS')
def test_image_service_missing_relation_data(self, configs):
configs.complete_contexts = MagicMock()
configs.complete_contexts.return_value = []
hooks.image_service_changed()
self.log.assert_called_with(
'image-service relation incomplete. Peer not ready?'
)
@patch.object(hooks, 'CONFIGS')
def test_image_service_with_relation_data(self, configs):
configs.complete_contexts = MagicMock()
configs.write = MagicMock()
configs.complete_contexts.return_value = ['image-service']
hooks.image_service_changed()
configs.write.assert_called_with('/etc/nova/nova.conf')
def test_compute_joined_no_migration_no_resize(self):
self.migration_enabled.return_value = False
hooks.compute_joined()
self.assertFalse(self.relation_set.called)
def test_compute_joined_with_ssh_migration(self):
self.migration_enabled.return_value = True
self.test_config.set('migration-auth-type', 'ssh')
self.public_ssh_key.return_value = 'foo'
hooks.compute_joined()
self.relation_set.assert_called_with(
relation_id=None,
ssh_public_key='foo',
migration_auth_type='ssh',
hostname='testserver',
)
hooks.compute_joined(rid='cloud-compute:2')
self.relation_set.assert_called_with(
relation_id='cloud-compute:2',
ssh_public_key='foo',
migration_auth_type='ssh',
hostname='testserver',
)
def test_compute_joined_with_resize(self):
self.migration_enabled.return_value = False
self.test_config.set('enable-resize', True)
self.public_ssh_key.return_value = 'bar'
hooks.compute_joined()
self.relation_set.assert_called_with(
relation_id=None,
nova_ssh_public_key='bar',
hostname='testserver',
)
hooks.compute_joined(rid='cloud-compute:2')
self.relation_set.assert_called_with(
relation_id='cloud-compute:2',
nova_ssh_public_key='bar',
hostname='testserver',
)
def test_compute_changed(self):
hooks.compute_changed()
self.assertTrue(self.import_keystone_ca_cert.called)
self.import_authorized_keys.assert_has_calls([
call(),
call(user='nova', prefix='nova'),
])
def test_compute_changed_nonstandard_authorized_keys_path(self):
self.migration_enabled.return_value = False
self.test_config.set('enable-resize', True)
hooks.compute_changed()
self.import_authorized_keys.assert_called_with(
user='nova',
prefix='nova',
)
def test_ceph_joined(self):
hooks.ceph_joined()
self.apt_install.assert_called_with(['ceph-common'], fatal=True)
self.service_restart.assert_called_with('libvirt-bin')
@patch.object(hooks, 'CONFIGS')
def test_ceph_changed_missing_relation_data(self, configs):
configs.complete_contexts = MagicMock()
configs.complete_contexts.return_value = []
hooks.ceph_changed()
self.log.assert_called_with(
'ceph relation incomplete. Peer not ready?'
)
@patch.object(hooks, 'CONFIGS')
def test_ceph_changed_no_keyring(self, configs):
configs.complete_contexts = MagicMock()
configs.complete_contexts.return_value = ['ceph']
self.ensure_ceph_keyring.return_value = False
hooks.ceph_changed()
self.log.assert_called_with(
'Could not create ceph keyring: peer not ready?'
)
@patch('nova_compute_context.service_name')
@patch.object(hooks, 'CONFIGS')
def test_ceph_changed_with_key_and_relation_data(self, configs,
service_name):
self.test_config.set('libvirt-image-backend', 'rbd')
self.is_request_complete.return_value = True
self.assert_libvirt_imagebackend_allowed.return_value = True
configs.complete_contexts = MagicMock()
configs.complete_contexts.return_value = ['ceph']
configs.write = MagicMock()
service_name.return_value = 'nova-compute'
self.ensure_ceph_keyring.return_value = True
hooks.ceph_changed()
ex = [
call('/var/lib/charm/nova-compute/ceph.conf'),
call('/etc/ceph/secret.xml'),
call('/etc/nova/nova.conf'),
]
self.assertEquals(ex, configs.write.call_args_list)
self.service_restart.assert_called_with('nova-compute')
@patch.object(hooks, 'CONFIGS')
def test_neutron_plugin_changed(self, configs):
self.relation_get.return_value = {'metadata-shared-secret':
'sharedsecret'}
hooks.neutron_plugin_changed()
self.assertTrue(self.apt_update.called)
self.apt_install.assert_called_with(['nova-api-metadata'],
fatal=True)
configs.write.assert_called_with('/etc/nova/nova.conf')
@patch.object(hooks, 'CONFIGS')
def test_neutron_plugin_changed_nometa_implicit(self, configs):
self.relation_get.return_value = {}
hooks.neutron_plugin_changed()
self.apt_purge.assert_called_with('nova-api-metadata',
fatal=True)
configs.write.assert_called_with('/etc/nova/nova.conf')
@patch.object(hooks, 'CONFIGS')
def test_neutron_plugin_changed_meta(self, configs):
self.relation_get.return_value = {'enable-metadata': 'True'}
hooks.neutron_plugin_changed()
self.apt_install.assert_called_with(['nova-api-metadata'],
fatal=True)
configs.write.assert_called_with('/etc/nova/nova.conf')
@patch.object(hooks, 'CONFIGS')
def test_neutron_plugin_changed_nometa_explicit(self, configs):
self.relation_get.return_value = {'enable-metadata': 'false'}
hooks.neutron_plugin_changed()
self.apt_purge.assert_called_with('nova-api-metadata',
fatal=True)
configs.write.assert_called_with('/etc/nova/nova.conf')
@patch.object(hooks, 'get_hugepage_number')
def test_neutron_plugin_joined_relid(self, get_hugepage_number):
get_hugepage_number.return_value = None
hooks.neutron_plugin_joined(relid='relid23')
self.relation_set.assert_called_with(
relation_id='relid23',
**{'hugepage_number': None}
)
@patch.object(hooks, 'get_hugepage_number')
def test_neutron_plugin_joined_huge(self, get_hugepage_number):
get_hugepage_number.return_value = 12
hooks.neutron_plugin_joined()
self.relation_set.assert_called_with(
relation_id=None,
**{'hugepage_number': 12}
)
@patch.object(hooks, 'get_hugepage_number')
def test_neutron_plugin_joined_remote_restart(self, get_hugepage_number):
get_hugepage_number.return_value = None
self.uuid.uuid4.return_value = 'e030b959-7207'
hooks.neutron_plugin_joined(remote_restart=True)
expect_rel_settings = {
'hugepage_number': None,
'restart-trigger': 'e030b959-7207',
}
self.relation_set.assert_called_with(
relation_id=None,
**expect_rel_settings
)