charm-nova-compute/unit_tests/test_nova_compute_hooks.py
Rodrigo Barbieri b1701e1b31 Prevent unnecessary nova-compute restarts on ceph_changed
Function 'is_broker_action_done' should return False when it
finds a response from ceph broker not marked done, in order
to trigger a nova restart. However, it also returns False if
there is no response data from ceph broker, triggering an
unecessary restart.

The function 'ceph_changed' is invoked under different remote
unit contexts when there are updates to the relation. When
querying the broker response, only the context of the remote
unit that is the broker can see the response, unless
specifically queried for that given unit.

The 'ceph_changed' invocations under a remote context that
are not the broker end up returning False in
'is_broker_action_done' and causing restarts, even after
the action is already marked done. This also happens on
'config-changed' hooks.

To fix this problem, the logic is now changed have each
'ceph_changed' invocation loop through units and process
the broker response, regardless of remote context.

This is an initial change to address the issue locally
in nova-compute charm. A later change will be worked on
to move the new helper methods to charmhelpers,
refactoring the existing ones there.

Change-Id: I2b41f8b252f4ccb68830e90c5e68456e15372bcf
Closes-bug: #1835045
2019-08-16 16:17:19 -03:00

972 lines
38 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 importlib
from mock import (
ANY,
call,
patch,
MagicMock
)
from nova_compute_hooks import update_nrpe_config
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
importlib.reload(hooks)
TO_PATCH = [
# charmhelpers.core.hookenv
'Hooks',
'config',
'local_unit',
'log',
'is_relation_made',
'relation_get',
'relation_ids',
'relation_set',
'service_name',
'related_units',
'remote_service_name',
# charmhelpers.core.host
'apt_install',
'apt_purge',
'apt_update',
'filter_installed_packages',
'restart_on_change',
'service_restart',
'is_container',
'service_running',
'service_start',
# charmhelpers.contrib.openstack.utils
'configure_installation_source',
'openstack_upgrade_available',
# charmhelpers.contrib.network.ip
'get_relation_ip',
# nova_compute_context
'nova_metadata_requirement',
# 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',
'network_manager',
'libvirt_daemon',
'configure_local_ephemeral_storage',
# misc_utils
'ensure_ceph_keyring',
'execd_preinstall',
'assert_libvirt_rbd_imagebackend_allowed',
'remove_libvirt_network',
# socket
'gethostname',
'create_sysctl',
'install_hugepages',
'uuid',
# unitdata
'unitdata',
# templating
'render',
'remove_old_packages',
'services',
]
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'
self.get_relation_ip.return_value = '10.0.0.50'
self.is_container.return_value = False
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)
@patch.object(hooks, 'ceph_changed')
@patch.object(hooks, 'neutron_plugin_joined')
def test_config_changed_with_upgrade(self, neutron_plugin_joined,
ceph_changed):
self.openstack_upgrade_available.return_value = True
self.service_running.return_value = True
def rel_ids(x):
return {'neutron-plugin': ['rid1'],
'ceph': ['ceph:0']}.get(x, [])
self.relation_ids.side_effect = rel_ids
self.related_units.return_value = ['ceph/0']
self.migration_enabled.return_value = False
hooks.config_changed()
self.assertTrue(self.do_openstack_upgrade.called)
neutron_plugin_joined.assert_called_with('rid1', remote_restart=True)
ceph_changed.assert_called_with(rid='ceph:0', unit='ceph/0')
self.configure_local_ephemeral_storage.assert_called_once_with()
self.service_start.assert_not_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)
self.migration_enabled.return_value = False
self.service_running.return_value = True
hooks.config_changed()
self.assertFalse(self.do_openstack_upgrade.called)
self.service_start.assert_not_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.migration_enabled.return_value = True
self.test_config.set('migration-auth-type', 'ssh')
self.relation_ids.return_value = [
'cloud-compute:0',
'cloud-compute:1'
]
self.service_running.return_value = True
hooks.config_changed()
ex = [
call('cloud-compute:0'),
call('cloud-compute:1'),
]
self.assertEqual(ex, compute_joined.call_args_list)
self.assertTrue(self.initialize_ssh_keys.called)
self.service_start.assert_not_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.test_config.set('enable-resize', True)
self.migration_enabled.return_value = False
self.relation_ids.return_value = [
'cloud-compute:0',
'cloud-compute:1'
]
self.service_running.return_value = True
hooks.config_changed()
ex = [
call('cloud-compute:0'),
call('cloud-compute:1'),
]
self.assertEqual(ex, compute_joined.call_args_list)
self.initialize_ssh_keys.assert_called_with(user='nova')
self.enable_shell.assert_called_with(user='nova')
self.service_start.assert_not_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.test_config.set('enable-resize', False)
self.migration_enabled.return_value = False
self.relation_ids.return_value = [
'cloud-compute:0',
'cloud-compute:1'
]
self.service_running.return_value = True
hooks.config_changed()
ex = [
call('cloud-compute:0'),
call('cloud-compute:1'),
]
self.assertEqual(ex, compute_joined.call_args_list)
self.disable_shell.assert_called_with(user='nova')
self.service_start.assert_not_called()
@patch.object(hooks, 'compute_joined')
def test_config_changed_no_upgrade_no_migration(self, compute_joined):
self.openstack_upgrade_available.return_value = False
self.migration_enabled.return_value = False
self.service_running.return_value = True
hooks.config_changed()
self.assertFalse(self.do_openstack_upgrade.called)
self.assertFalse(compute_joined.called)
self.service_start.assert_not_called()
@patch.object(hooks, 'compute_joined')
def test_config_changed_with_sysctl(self, compute_joined):
self.migration_enabled.return_value = False
self.service_running.return_value = True
self.test_config.set(
'sysctl',
'{foo : bar}'
)
hooks.config_changed()
self.create_sysctl.assert_called_with(
'{foo : bar}',
'/etc/sysctl.d/50-nova-compute.conf',
ignore=True)
@patch.object(hooks, 'compute_joined')
def test_config_changed_with_sysctl_in_container(self, compute_joined):
self.migration_enabled.return_value = False
self.is_container.return_value = True
self.service_running.return_value = True
self.test_config.set(
'sysctl',
'{foo : bar}'
)
hooks.config_changed()
self.create_sysctl.assert_not_called()
self.service_start.assert_not_called()
@patch.object(hooks, 'compute_joined')
def test_config_changed_no_nrpe(self, compute_joined):
self.openstack_upgrade_available.return_value = False
self.migration_enabled.return_value = False
self.is_relation_made.return_value = False
self.service_running.return_value = True
hooks.config_changed()
self.assertFalse(self.update_nrpe_config.called)
self.service_start.assert_not_called()
@patch.object(hooks, 'compute_joined')
def test_config_changed_nrpe(self, compute_joined):
self.openstack_upgrade_available.return_value = False
self.migration_enabled.return_value = False
self.is_relation_made.return_value = True
self.service_running.return_value = True
hooks.config_changed()
self.assertTrue(self.update_nrpe_config.called)
self.service_start.assert_not_called()
@patch.object(hooks, 'compute_joined')
def test_config_changed_invalid_migration(self, compute_joined):
self.migration_enabled.return_value = True
self.service_running.return_value = True
self.test_config.set('migration-auth-type', 'none')
with self.assertRaises(Exception) as context:
hooks.config_changed()
self.assertEqual(
context.exception.message,
'Invalid migration-auth-type')
self.service_start.assert_not_called()
@patch.object(hooks, 'compute_joined')
def test_config_changed_use_multipath_false(self,
compute_joined):
self.service_running.return_value = True
self.test_config.set('use-multipath', False)
hooks.config_changed()
self.assertEqual(self.filter_installed_packages.call_count, 0)
self.service_start.assert_not_called()
@patch.object(hooks, 'compute_joined')
def test_config_changed_use_multipath_true(self,
compute_joined):
self.test_config.set('use-multipath', True)
self.filter_installed_packages.return_value = []
self.service_running.return_value = True
hooks.config_changed()
self.assertEqual(self.filter_installed_packages.call_count, 1)
self.apt_install.assert_called_with(hooks.MULTIPATH_PACKAGES,
fatal=True)
self.service_start.assert_not_called()
@patch.object(hooks, 'compute_joined')
def test_config_changed_iscsid_not_running(self,
compute_joined):
self.service_running.return_value = False
hooks.config_changed()
self.service_start.assert_called_once_with('iscsid')
@patch('nova_compute_hooks.nrpe')
@patch('nova_compute_hooks.services')
@patch('charmhelpers.core.hookenv')
def test_nrpe_services_no_qemu_kvm(self, hookenv, services, nrpe):
'''
The qemu-kvm service is not monitored by NRPE, since it's one-shot.
'''
services.return_value = ['libvirtd', 'qemu-kvm', 'libvirt-bin']
update_nrpe_config()
nrpe.add_init_service_checks.assert_called_with(
ANY, ['libvirtd', 'libvirt-bin'], ANY)
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.assertEqual([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',
'private-address': '10.0.0.50',
})
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',
'private-address': '10.0.0.50',
})
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',
'private-address': '10.0.0.50',
})
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',
'private-address': '10.0.0.50',
})
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):
self.libvirt_daemon.return_value = 'libvirt-bin'
hooks.ceph_joined()
self.apt_install.assert_called_with(['ceph-common'], fatal=True)
self.service_restart.assert_called_with('libvirt-bin')
self.libvirt_daemon.assert_called()
@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.object(hooks, '_handle_ceph_request')
@patch.object(hooks, 'create_libvirt_secret')
@patch('nova_compute_context.service_name')
@patch.object(hooks, 'CONFIGS')
def test_ceph_changed_with_key_and_relation_data(self, configs,
service_name,
create_libvirt_secret,
_handle_ceph_request):
configs.complete_contexts = MagicMock()
configs.complete_contexts.return_value = ['ceph']
self.ensure_ceph_keyring.return_value = True
configs.write = MagicMock()
service_name.return_value = 'nova-compute'
key = {'data': 'key'}
self.relation_get.return_value = key
hooks.ceph_changed()
ex = [
call('/var/lib/charm/nova-compute/ceph.conf'),
call('/etc/ceph/secret.xml'),
call('/etc/nova/nova.conf'),
]
self.assertEqual(ex, configs.write.call_args_list)
create_libvirt_secret.assert_called_once_with(
secret_file='/etc/ceph/secret.xml', key=key,
secret_uuid=hooks.CEPH_SECRET_UUID)
@patch.object(hooks, 'get_ceph_request')
@patch.object(hooks, 'get_request_states')
def test__handle_ceph_request_send_request(
self, get_request_states, get_ceph_request):
request = hooks.CephBrokerRq()
get_ceph_request.return_value = request
get_request_states.return_value = {
'ceph:43': {'complete': False, 'sent': False}
}
self.relation_ids.return_value = ['ceph:43']
hooks._handle_ceph_request()
get_ceph_request.assert_called_once_with()
get_request_states.assert_called_once_with(request, relation='ceph')
self.relation_set.assert_called_once_with(
relation_id='ceph:43', broker_req=request.request)
@patch.object(hooks, 'get_ceph_request')
@patch.object(hooks, 'get_request_states')
def test__handle_ceph_request_already_sent(
self, get_request_states, get_ceph_request):
request = hooks.CephBrokerRq()
get_ceph_request.return_value = request
get_request_states.return_value = {
'ceph:43': {'complete': False, 'sent': True}
}
hooks._handle_ceph_request()
get_ceph_request.assert_called_once_with()
get_request_states.assert_called_once_with(request, relation='ceph')
self.relation_set.assert_not_called()
@patch.object(hooks, 'is_broker_action_done')
@patch.object(hooks, 'get_ceph_request')
@patch.object(hooks, 'get_request_states')
@patch.object(hooks, '_get_broker_rid_unit_for_previous_request')
def test__handle_ceph_request_complete_no_broker(
self, _get_broker_rid_unit_for_previous_request,
get_request_states, get_ceph_request, is_broker_action_done):
request = hooks.CephBrokerRq()
get_ceph_request.return_value = request
get_request_states.return_value = {
'ceph:43': {'complete': True, 'sent': True}
}
_get_broker_rid_unit_for_previous_request.return_value = None, None
hooks._handle_ceph_request()
is_broker_action_done.assert_not_called()
get_ceph_request.assert_called_once_with()
get_request_states.assert_called_once_with(request, relation='ceph')
@patch.object(hooks, 'service_restart')
@patch.object(hooks, 'mark_broker_action_done')
@patch.object(hooks, 'is_broker_action_done')
@patch.object(hooks, 'get_ceph_request')
@patch.object(hooks, 'get_request_states')
@patch.object(hooks, '_get_broker_rid_unit_for_previous_request')
def test__handle_ceph_request_complete_not_action_done(
self, _get_broker_rid_unit_for_previous_request,
get_request_states, get_ceph_request, is_broker_action_done,
mark_broker_action_done, service_restart):
request = hooks.CephBrokerRq()
get_ceph_request.return_value = request
get_request_states.return_value = {
'ceph:43': {'complete': True, 'sent': True}
}
_get_broker_rid_unit_for_previous_request.return_value = 'ceph:43',\
'ceph-mon/0'
is_broker_action_done.return_value = False
hooks._handle_ceph_request()
mark_broker_action_done.assert_called_once_with(
'nova_compute_restart', 'ceph:43', 'ceph-mon/0')
service_restart.assert_called_once_with('nova-compute')
is_broker_action_done.assert_called_once_with(
'nova_compute_restart', 'ceph:43', 'ceph-mon/0')
get_ceph_request.assert_called_once_with()
get_request_states.assert_called_once_with(request, relation='ceph')
@patch.object(hooks, 'service_restart')
@patch.object(hooks, 'is_broker_action_done')
@patch.object(hooks, 'get_ceph_request')
@patch.object(hooks, 'get_request_states')
@patch.object(hooks, '_get_broker_rid_unit_for_previous_request')
def test__handle_ceph_request_complete_no_restart(
self, _get_broker_rid_unit_for_previous_request,
get_request_states, get_ceph_request, is_broker_action_done,
service_restart):
request = hooks.CephBrokerRq()
get_ceph_request.return_value = request
get_request_states.return_value = {
'ceph:43': {'complete': True, 'sent': True}
}
_get_broker_rid_unit_for_previous_request.return_value = 'ceph:43',\
'ceph-mon/0'
is_broker_action_done.return_value = True
hooks._handle_ceph_request()
service_restart.assert_not_called()
is_broker_action_done.assert_called_once_with(
'nova_compute_restart', 'ceph:43', 'ceph-mon/0')
get_ceph_request.assert_called_once_with()
get_request_states.assert_called_once_with(request, relation='ceph')
@patch.object(hooks, 'get_broker_rsp_key')
@patch.object(hooks, 'get_previous_request')
def test__get_broker_rid_unit_for_previous_request(
self, get_previous_request, get_broker_rsp_key):
request = hooks.CephBrokerRq()
get_broker_rsp_key.return_value = "my_key"
get_previous_request.return_value = request
self.relation_ids.return_value = ['ceph:43']
self.related_units.return_value = ['ceph-mon/0', 'ceph-mon/1']
self.relation_get.side_effect = [{}, {
'my_key': '{"api-version": 1, "ops": [], '
'"request-id": "' + request.request_id + '"}'
}]
rid, unit = hooks._get_broker_rid_unit_for_previous_request()
get_previous_request.assert_called_once_with('ceph:43')
get_broker_rsp_key.assert_called_once_with()
self.relation_get.assert_has_calls = [
call(rid='ceph:43', unit='ceph-mon/0'),
call(rid='ceph:43', unit='ceph-mon/1')
]
self.assertEqual('ceph-mon/1', unit)
self.assertEqual('ceph:43', rid)
@patch.object(hooks, 'get_broker_rsp_key')
@patch.object(hooks, 'get_previous_request')
def test__get_broker_rid_unit_for_previous_request_not_found(
self, get_previous_request, get_broker_rsp_key):
request = hooks.CephBrokerRq()
get_broker_rsp_key.return_value = "my_key"
get_previous_request.return_value = request
self.relation_ids.return_value = ['ceph:43']
self.related_units.return_value = ['ceph-mon/0', 'ceph-mon/1']
self.relation_get.side_effect = [{}, {}]
rid, unit = hooks._get_broker_rid_unit_for_previous_request()
get_previous_request.assert_called_once_with('ceph:43')
get_broker_rsp_key.assert_called_once_with()
self.relation_get.assert_has_calls = [
call(rid='ceph:43', unit='ceph-mon/0'),
call(rid='ceph:43', unit='ceph-mon/1')
]
self.assertIsNone(unit)
self.assertIsNone(rid)
@patch('charmhelpers.contrib.storage.linux.ceph.CephBrokerRq'
'.add_op_request_access_to_group')
@patch('charmhelpers.contrib.storage.linux.ceph.CephBrokerRq'
'.add_op_create_pool')
@patch('uuid.uuid1')
def test_get_ceph_request(self, uuid1, mock_create_pool,
mock_request_access):
self.assert_libvirt_rbd_imagebackend_allowed.return_value = True
self.test_config.set('rbd-pool', 'nova')
self.test_config.set('ceph-osd-replication-count', 3)
self.test_config.set('ceph-pool-weight', 28)
uuid1.return_value = 'my_uuid'
expected = hooks.CephBrokerRq(request_id="my_uuid")
result = hooks.get_ceph_request()
mock_create_pool.assert_not_called()
mock_request_access.assert_not_called()
self.assertEqual(expected, result)
@patch('charmhelpers.contrib.storage.linux.ceph.CephBrokerRq'
'.add_op_request_access_to_group')
@patch('charmhelpers.contrib.storage.linux.ceph.CephBrokerRq'
'.add_op_create_pool')
@patch('uuid.uuid1')
def test_get_ceph_request_rbd(self, uuid1, mock_create_pool,
mock_request_access):
self.assert_libvirt_rbd_imagebackend_allowed.return_value = True
self.test_config.set('rbd-pool', 'nova')
self.test_config.set('ceph-osd-replication-count', 3)
self.test_config.set('ceph-pool-weight', 28)
self.test_config.set('libvirt-image-backend', 'rbd')
uuid1.return_value = 'my_uuid'
expected = hooks.CephBrokerRq(request_id="my_uuid")
result = hooks.get_ceph_request()
mock_create_pool.assert_called_with(name='nova', replica_count=3,
weight=28,
group='vms', app_name='rbd')
mock_request_access.assert_not_called()
self.assertEqual(expected, result)
@patch('charmhelpers.contrib.storage.linux.ceph.CephBrokerRq'
'.add_op_request_access_to_group')
@patch('charmhelpers.contrib.storage.linux.ceph.CephBrokerRq'
'.add_op_create_pool')
@patch('uuid.uuid1')
def test_get_ceph_request_perms(self, uuid1, mock_create_pool,
mock_request_access):
self.assert_libvirt_rbd_imagebackend_allowed.return_value = True
self.test_config.set('rbd-pool', 'nova')
self.test_config.set('ceph-osd-replication-count', 3)
self.test_config.set('ceph-pool-weight', 28)
self.test_config.set('libvirt-image-backend', 'rbd')
self.test_config.set('restrict-ceph-pools', True)
uuid1.return_value = 'my_uuid'
expected = hooks.CephBrokerRq(request_id="my_uuid")
result = hooks.get_ceph_request()
mock_create_pool.assert_called_with(name='nova', replica_count=3,
weight=28,
group='vms', app_name='rbd')
mock_request_access.assert_has_calls([
call(name='volumes',
object_prefix_permissions={'class-read': ['rbd_children']},
permission='rwx'),
call(name='images',
object_prefix_permissions={'class-read': ['rbd_children']},
permission='rwx'),
call(name='vms',
object_prefix_permissions={'class-read': ['rbd_children']},
permission='rwx'),
])
self.assertEqual(expected, result)
@patch.object(hooks, 'service_restart_handler')
@patch.object(hooks, 'CONFIGS')
def test_neutron_plugin_changed(self, configs,
service_restart_handler):
self.nova_metadata_requirement.return_value = (True,
'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')
service_restart_handler.assert_called_with(
default_service='nova-compute')
@patch.object(hooks, 'service_restart_handler')
@patch.object(hooks, 'CONFIGS')
def test_neutron_plugin_changed_nometa(self, configs,
service_restart_handler):
self.nova_metadata_requirement.return_value = (False, None)
hooks.neutron_plugin_changed()
self.apt_purge.assert_called_with('nova-api-metadata',
fatal=True)
configs.write.assert_called_with('/etc/nova/nova.conf')
service_restart_handler.assert_called_with(
default_service='nova-compute')
@patch.object(hooks, 'service_restart_handler')
@patch.object(hooks, 'CONFIGS')
def test_neutron_plugin_changed_meta(self, configs,
service_restart_handler):
self.nova_metadata_requirement.return_value = (True, None)
hooks.neutron_plugin_changed()
self.apt_install.assert_called_with(['nova-api-metadata'],
fatal=True)
configs.write.assert_called_with('/etc/nova/nova.conf')
service_restart_handler.assert_called_with(
default_service='nova-compute')
@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')
expect_rel_settings = {
'hugepage_number': None,
'default_availability_zone': 'nova',
}
self.relation_set.assert_called_with(
relation_id='relid23',
**expect_rel_settings
)
@patch('os.environ.get')
@patch.object(hooks, 'get_hugepage_number')
def test_neutron_plugin_joined_relid_juju_az(self,
get_hugepage_number,
mock_env_get):
self.test_config.set('customize-failure-domain', True)
def environ_get_side_effect(key):
return {
'JUJU_AVAILABILITY_ZONE': 'az1',
}[key]
mock_env_get.side_effect = environ_get_side_effect
get_hugepage_number.return_value = None
hooks.neutron_plugin_joined(relid='relid23')
expect_rel_settings = {
'hugepage_number': None,
'default_availability_zone': 'az1',
}
self.relation_set.assert_called_with(
relation_id='relid23',
**expect_rel_settings
)
@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()
expect_rel_settings = {
'hugepage_number': 12,
'default_availability_zone': 'nova',
}
self.relation_set.assert_called_with(
relation_id=None,
**expect_rel_settings
)
@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',
'default_availability_zone': 'nova',
}
self.relation_set.assert_called_with(
relation_id=None,
**expect_rel_settings
)
@patch.object(hooks, 'is_unit_paused_set')
def test_service_restart_handler(self,
is_unit_paused_set):
self.relation_get.return_value = None
mock_kv = MagicMock()
mock_kv.get.return_value = None
self.unitdata.kv.return_value = mock_kv
hooks.service_restart_handler(default_service='foorbar')
self.relation_get.assert_called_with(
attribute='restart-nonce',
unit=None,
rid=None
)
is_unit_paused_set.assert_not_called()
@patch.object(hooks, 'is_unit_paused_set')
def test_service_restart_handler_with_service(self,
is_unit_paused_set):
self.relation_get.side_effect = ['nonce', 'foobar-service']
mock_kv = MagicMock()
mock_kv.get.return_value = None
self.unitdata.kv.return_value = mock_kv
is_unit_paused_set.return_value = False
hooks.service_restart_handler()
self.relation_get.assert_has_calls([
call(attribute='restart-nonce',
unit=None,
rid=None),
call(attribute='remote-service',
unit=None,
rid=None),
])
self.service_restart.assert_called_with('foobar-service')
mock_kv.set.assert_called_with('restart-nonce',
'nonce')
self.assertTrue(mock_kv.flush.called)
@patch.object(hooks, 'is_unit_paused_set')
def test_service_restart_handler_when_paused(self,
is_unit_paused_set):
self.relation_get.side_effect = ['nonce', 'foobar-service']
mock_kv = MagicMock()
mock_kv.get.return_value = None
self.unitdata.kv.return_value = mock_kv
is_unit_paused_set.return_value = True
hooks.service_restart_handler()
self.relation_get.assert_has_calls([
call(attribute='restart-nonce',
unit=None,
rid=None),
])
self.service_restart.assert_not_called()
mock_kv.set.assert_called_with('restart-nonce',
'nonce')
self.assertTrue(mock_kv.flush.called)
def test_ceph_access_incomplete(self):
self.relation_get.return_value = None
self.test_config.set('virt-type', 'kvm')
hooks.ceph_access()
self.relation_get.assert_has_calls([
call('key', None, None),
call('secret-uuid', None, None),
])
self.render.assert_not_called()
self.create_libvirt_secret.assert_not_called()
def test_ceph_access_lxd(self):
self.relation_get.side_effect = ['mykey', 'uuid2']
self.remote_service_name.return_value = 'cinder-ceph'
self.test_config.set('virt-type', 'lxd')
hooks.ceph_access()
self.relation_get.assert_has_calls([
call('key', None, None),
call('secret-uuid', None, None),
])
self.render.assert_not_called()
self.create_libvirt_secret.assert_not_called()
self.ensure_ceph_keyring.assert_called_with(
service='cinder-ceph',
user='nova',
group='nova',
key='mykey'
)
def test_ceph_access_complete(self):
self.relation_get.side_effect = ['mykey', 'uuid2']
self.remote_service_name.return_value = 'cinder-ceph'
self.test_config.set('virt-type', 'kvm')
hooks.ceph_access()
self.relation_get.assert_has_calls([
call('key', None, None),
call('secret-uuid', None, None),
])
self.render.assert_called_with(
'secret.xml',
'/etc/ceph/secret-cinder-ceph.xml',
context={'ceph_secret_uuid': 'uuid2',
'service_name': 'cinder-ceph'}
)
self.create_libvirt_secret.assert_called_with(
secret_file='/etc/ceph/secret-cinder-ceph.xml',
secret_uuid='uuid2',
key='mykey',
)
self.ensure_ceph_keyring.assert_called_with(
service='cinder-ceph',
user='nova',
group='nova',
key='mykey'
)
def test_secrets_storage_relation_joined(self):
self.get_relation_ip.return_value = '10.23.1.2'
self.gethostname.return_value = 'testhost'
hooks.secrets_storage_joined()
self.get_relation_ip.assert_called_with('secrets-storage')
self.relation_set.assert_called_with(
relation_id=None,
secret_backend='charm-vaultlocker',
isolated=True,
access_address='10.23.1.2',
hostname='testhost'
)
self.gethostname.assert_called_once_with()
def test_secrets_storage_relation_changed(self,):
self.relation_get.return_value = None
hooks.secrets_storage_changed()
self.configure_local_ephemeral_storage.assert_called_once_with()
def test_cloud_credentials_joined(self):
self.local_unit.return_value = 'nova-compute-cell1/2'
hooks.cloud_credentials_joined()
self.relation_set.assert_called_with(username='nova_compute_cell1')
@patch.object(hooks, 'CONFIGS')
def test_cloud_credentials_changed(self, mock_CONFIGS):
hooks.cloud_credentials_changed()
mock_CONFIGS.write.assert_called_with('/etc/nova/nova.conf')
@patch.object(hooks.grp, 'getgrnam')
def test_upgrade_charm(self, getgrnam):
grp_mock = MagicMock()
grp_mock.gr_gid = None
getgrnam.return_value = grp_mock
self.remove_old_packages.return_value = False
hooks.upgrade_charm()
self.remove_old_packages.assert_called_once_with()
self.assertFalse(self.service_restart.called)
@patch.object(hooks.grp, 'getgrnam')
def test_upgrade_charm_purge(self, getgrnam):
grp_mock = MagicMock()
grp_mock.gr_gid = None
getgrnam.return_value = grp_mock
self.remove_old_packages.return_value = True
self.services.return_value = ['nova-compute']
hooks.upgrade_charm()
self.remove_old_packages.assert_called_once_with()
self.service_restart.assert_called_once_with('nova-compute')