charm-nova-compute/unit_tests/test_nova_compute_utils.py
James Page d10dd7795e Add support for instance storage encryption
Add support for encryption of the underlying block device providing
storage for local instances.

This commit introduces a new juju storage binding and configuration
option to provide a single block device for use for local instance
storage; this block device is formatted and mounted at
/var/lib/nova/instances.  In a MAAS deployment, this could be a
bcache fronted device.

The configuration option is preferred over the Juju storage binding
if both are supplied.

This block device can optionally be encrypted using dm-crypt/LUKS
with encryption keys stored in Hashicorp Vault using vaultlocker.

vaultlocker ensures that keys are never persisted to local storage,
providing assurance around security of data at rest in the event
that disks/server are stolen.

Charm support is implemented using a new configuration option 'encrypt'
which when set enforces a mandatory relationship to an instance
of the vault application.

Copy the 'ephemeral-unmount' config option and assocaited code from
the ceph-osd and swift-storage charms to enable testing in cloudy
environments.

Change-Id: I772baa61f45ff430f706ec4864f3018488026148
2018-05-15 08:27:10 +01:00

955 lines
37 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 tempfile
import nova_compute_context as compute_context
import nova_compute_utils as utils
from mock import (
patch,
MagicMock,
call
)
from test_utils import (
CharmTestCase,
patch_open,
TestKV,
)
VIRSH_NET_LIST = """ Name State Autostart Persistent
----------------------------------------------------------
somenet active yes yes
default active yes yes
altnet active yes yes
"""
TO_PATCH = [
'apt_install',
'apt_update',
'config',
'os_release',
'log',
'related_units',
'relation_ids',
'relation_get',
'service_restart',
'mkdir',
'install_alternative',
'MetadataServiceContext',
'lsb_release',
'charm_dir',
'hugepage_support',
'rsync',
'Fstab',
'os_application_version_set',
'lsb_release',
'storage_list',
'storage_get',
'vaultlocker',
'kv',
'check_call',
'mkfs_xfs',
'is_block_device',
'is_device_mounted',
'fstab_add',
'mount',
]
class NovaComputeUtilsTests(CharmTestCase):
def setUp(self):
super(NovaComputeUtilsTests, self).setUp(utils, TO_PATCH)
self.config.side_effect = self.test_config.get
self.charm_dir.return_value = 'mycharm'
self.lsb_release.return_value = {'DISTRIB_CODENAME': 'precise'}
self.test_kv = TestKV()
self.kv.return_value = self.test_kv
@patch.object(utils, 'nova_metadata_requirement')
@patch.object(utils, 'network_manager')
@patch('platform.machine')
def test_determine_packages_nova_network(self, machine,
net_man, en_meta):
self.os_release.return_value = 'icehouse'
en_meta.return_value = (False, None)
net_man.return_value = 'flatdhcpmanager'
machine.return_value = 'x86_64'
self.relation_ids.return_value = []
result = utils.determine_packages()
ex = utils.BASE_PACKAGES + [
'nova-api',
'nova-network',
'nova-compute-kvm'
]
self.assertTrue(ex == result)
@patch.object(utils, 'nova_metadata_requirement')
@patch.object(utils, 'network_manager')
@patch('platform.machine')
def test_determine_packages_nova_network_ocata(self, machine,
net_man, en_meta):
self.os_release.return_value = 'ocata'
en_meta.return_value = (False, None)
net_man.return_value = 'flatdhcpmanager'
machine.return_value = 'x86_64'
self.relation_ids.return_value = []
result = utils.determine_packages()
ex = utils.BASE_PACKAGES + [
'nova-compute-kvm'
]
self.assertTrue(ex == result)
@patch.object(utils, 'nova_metadata_requirement')
@patch.object(utils, 'neutron_plugin')
@patch.object(utils, 'network_manager')
@patch('platform.machine')
def test_determine_packages_neutron(self, machine, net_man,
n_plugin, en_meta):
en_meta.return_value = (False, None)
net_man.return_value = 'neutron'
n_plugin.return_value = 'ovs'
machine.return_value = 'x86_64'
self.relation_ids.return_value = []
result = utils.determine_packages()
ex = utils.BASE_PACKAGES + ['nova-compute-kvm']
self.assertTrue(ex == result)
@patch.object(utils, 'nova_metadata_requirement')
@patch.object(utils, 'neutron_plugin')
@patch.object(utils, 'network_manager')
@patch('platform.machine')
def test_determine_packages_neutron_aarch64_xenial(self, machine,
net_man, n_plugin,
en_meta):
self.lsb_release.return_value = {
'DISTRIB_CODENAME': 'xenial'
}
en_meta.return_value = (False, None)
net_man.return_value = 'neutron'
n_plugin.return_value = 'ovs'
machine.return_value = 'aarch64'
self.relation_ids.return_value = []
result = utils.determine_packages()
ex = utils.BASE_PACKAGES + ['nova-compute-kvm', 'qemu-efi']
self.assertTrue(ex == result)
@patch.object(utils, 'nova_metadata_requirement')
@patch.object(utils, 'neutron_plugin')
@patch.object(utils, 'network_manager')
@patch('platform.machine')
def test_determine_packages_neutron_aarch64_trusty(self, machine,
net_man, n_plugin,
en_meta):
self.lsb_release.return_value = {
'DISTRIB_CODENAME': 'trusty'
}
en_meta.return_value = (False, None)
net_man.return_value = 'neutron'
n_plugin.return_value = 'ovs'
machine.return_value = 'aarch64'
self.relation_ids.return_value = []
result = utils.determine_packages()
ex = utils.BASE_PACKAGES + ['nova-compute-kvm']
self.assertEqual(ex, result)
@patch.object(utils, 'nova_metadata_requirement')
@patch.object(utils, 'neutron_plugin')
@patch.object(utils, 'network_manager')
@patch('platform.machine')
def test_determine_packages_neutron_ceph(self, machine,
net_man, n_plugin, en_meta):
en_meta.return_value = (False, None)
net_man.return_value = 'neutron'
n_plugin.return_value = 'ovs'
machine.return_value = 'x86_64'
self.relation_ids.return_value = ['ceph:0']
result = utils.determine_packages()
ex = (utils.BASE_PACKAGES + ['ceph-common', 'nova-compute-kvm'])
self.assertEqual(ex, result)
@patch.object(utils, 'nova_metadata_requirement')
@patch.object(utils, 'neutron_plugin')
@patch.object(utils, 'network_manager')
def test_determine_packages_metadata(self, net_man,
n_plugin, en_meta):
en_meta.return_value = (True, None)
net_man.return_value = 'bob'
n_plugin.return_value = 'ovs'
self.relation_ids.return_value = []
result = utils.determine_packages()
self.assertTrue('nova-api-metadata' in result)
@patch.object(utils, 'nova_metadata_requirement')
@patch.object(utils, 'network_manager')
def test_resource_map_nova_network_no_multihost(self, net_man, en_meta):
self.os_release.return_value = 'icehouse'
self.test_config.set('multi-host', 'no')
en_meta.return_value = (False, None)
net_man.return_value = 'flatdhcpmanager'
result = utils.resource_map()
ex = {
'/etc/default/libvirt-bin': {
'contexts': [],
'services': ['libvirt-bin']
},
'/etc/libvirt/qemu.conf': {
'contexts': [],
'services': ['libvirt-bin']
},
'/etc/nova/nova.conf': {
'contexts': [],
'services': ['nova-compute']
},
'/etc/ceph/secret.xml': {
'contexts': [],
'services': []
},
'/var/lib/charm/nova_compute/ceph.conf': {
'contexts': [],
'services': ['nova-compute']
},
'/etc/default/qemu-kvm': {
'contexts': [],
'services': ['qemu-kvm']
},
'/etc/init/libvirt-bin.override': {
'contexts': [],
'services': ['libvirt-bin']
},
'/etc/libvirt/libvirtd.conf': {
'contexts': [],
'services': ['libvirt-bin']
},
'/etc/apparmor.d/usr.bin.nova-compute': {
'contexts': [],
'services': ['nova-compute']
},
}
# Mocking contexts is tricky but we can still test that
# the correct files are monitored and the correct services
# will be started
self.assertEqual(set(ex.keys()), set(result.keys()))
for k in ex.keys():
self.assertEqual(set(ex[k]['services']),
set(result[k]['services']))
@patch.object(utils, 'nova_metadata_requirement')
@patch.object(utils, 'network_manager')
def test_resource_map_nova_network_ocata(self, net_man, en_meta):
self.os_release.return_value = 'ocata'
self.test_config.set('multi-host', 'yes')
en_meta.return_value = (False, None)
net_man.return_value = 'flatdhcpmanager'
result = utils.resource_map()
ex = {
'/etc/default/libvirt-bin': {
'contexts': [],
'services': ['libvirtd']
},
'/etc/libvirt/qemu.conf': {
'contexts': [],
'services': ['libvirtd']
},
'/etc/nova/nova.conf': {
'contexts': [],
'services': ['nova-compute']
},
'/etc/ceph/secret.xml': {
'contexts': [],
'services': []
},
'/var/lib/charm/nova_compute/ceph.conf': {
'contexts': [],
'services': ['nova-compute']
},
'/etc/default/qemu-kvm': {
'contexts': [],
'services': ['qemu-kvm']
},
'/etc/init/libvirt-bin.override': {
'contexts': [],
'services': ['libvirtd']
},
'/etc/libvirt/libvirtd.conf': {
'contexts': [],
'services': ['libvirtd']
},
'/etc/apparmor.d/usr.bin.nova-compute': {
'contexts': [],
'services': ['nova-compute']
},
}
# Mocking contexts is tricky but we can still test that
# the correct files are monitored and the correct services
# will be started
self.assertEqual(set(ex.keys()), set(result.keys()))
for k in ex.keys():
self.assertEqual(set(ex[k]['services']),
set(result[k]['services']))
@patch.object(utils, 'nova_metadata_requirement')
@patch.object(utils, 'network_manager')
def test_resource_map_nova_network(self, net_man, en_meta):
self.os_release.return_value = 'icehouse'
en_meta.return_value = (False, None)
self.test_config.set('multi-host', 'yes')
net_man.return_value = 'flatdhcpmanager'
result = utils.resource_map()
ex = {
'/etc/default/libvirt-bin': {
'contexts': [],
'services': ['libvirt-bin']
},
'/etc/libvirt/qemu.conf': {
'contexts': [],
'services': ['libvirt-bin']
},
'/etc/nova/nova.conf': {
'contexts': [],
'services': ['nova-compute', 'nova-api', 'nova-network']
},
'/etc/ceph/secret.xml': {
'contexts': [],
'services': []
},
'/var/lib/charm/nova_compute/ceph.conf': {
'contexts': [],
'services': ['nova-compute']
},
'/etc/default/qemu-kvm': {
'contexts': [],
'services': ['qemu-kvm']
},
'/etc/init/libvirt-bin.override': {
'contexts': [],
'services': ['libvirt-bin']
},
'/etc/libvirt/libvirtd.conf': {
'contexts': [],
'services': ['libvirt-bin']
},
'/etc/apparmor.d/usr.bin.nova-network': {
'contexts': [],
'services': ['nova-network']
},
'/etc/apparmor.d/usr.bin.nova-compute': {
'contexts': [],
'services': ['nova-compute']
},
'/etc/apparmor.d/usr.bin.nova-api': {
'contexts': [],
'services': ['nova-api']
},
}
# Mocking contexts is tricky but we can still test that
# the correct files are monitored and the correct services
# will be started
self.assertEqual(set(ex.keys()), set(result.keys()))
for k in ex.keys():
self.assertEqual(set(ex[k]['services']),
set(result[k]['services']))
def _test_resource_map_neutron(self, net_man, en_meta,
libvirt_daemon):
en_meta.return_value = (False, None)
self.test_config.set('multi-host', 'yes')
net_man.return_value = 'neutron'
result = utils.resource_map()
ex = {
'/etc/default/libvirt-bin': {
'contexts': [],
'services': [libvirt_daemon]
},
'/etc/libvirt/qemu.conf': {
'contexts': [],
'services': [libvirt_daemon]
},
'/etc/nova/nova.conf': {
'contexts': [],
'services': ['nova-compute']
},
'/etc/ceph/secret.xml': {
'contexts': [],
'services': []
},
'/var/lib/charm/nova_compute/ceph.conf': {
'contexts': [],
'services': ['nova-compute']
},
'/etc/default/qemu-kvm': {
'contexts': [],
'services': ['qemu-kvm']
},
'/etc/init/libvirt-bin.override': {
'contexts': [],
'services': [libvirt_daemon]
},
'/etc/libvirt/libvirtd.conf': {
'contexts': [],
'services': [libvirt_daemon]
},
'/etc/apparmor.d/usr.bin.nova-compute': {
'contexts': [],
'services': ['nova-compute']
},
}
# Mocking contexts is tricky but we can still test that
# the correct files are monitored and the correct services
# will be started
self.assertEqual(set(ex.keys()), set(result.keys()))
for k in ex.keys():
self.assertEqual(set(ex[k]['services']),
set(result[k]['services']))
@patch.object(utils, 'nova_metadata_requirement')
@patch.object(utils, 'network_manager')
def test_resource_map_neutron(self, net_man, en_meta):
self.os_release.return_value = 'diablo'
self._test_resource_map_neutron(net_man, en_meta, 'libvirt-bin')
@patch.object(utils, 'nova_metadata_requirement')
@patch.object(utils, 'network_manager')
def test_resource_map_neutron_yakkety(self, net_man, en_meta,):
self.lsb_release.return_value = {'DISTRIB_CODENAME': 'yakkety'}
self.os_release.return_value = 'diablo'
self._test_resource_map_neutron(net_man, en_meta, 'libvirtd')
@patch.object(utils, 'nova_metadata_requirement')
@patch.object(utils, 'neutron_plugin')
@patch.object(utils, 'network_manager')
def test_resource_map_metadata(self, net_man, _plugin, _metadata):
_metadata.return_value = (True, None)
net_man.return_value = 'bob'
_plugin.return_value = 'ovs'
self.relation_ids.return_value = []
self.os_release.return_value = 'diablo'
result = utils.resource_map()['/etc/nova/nova.conf']['services']
self.assertTrue('nova-api-metadata' in result)
def fake_user(self, username='foo'):
user = MagicMock()
user.pw_dir = '/home/' + username
return user
@patch('builtins.open')
@patch('pwd.getpwnam')
def test_public_ssh_key_not_found(self, getpwnam, _open):
_open.side_effect = Exception
getpwnam.return_value = self.fake_user('foo')
self.assertEqual(None, utils.public_ssh_key())
@patch('pwd.getpwnam')
def test_public_ssh_key(self, getpwnam):
getpwnam.return_value = self.fake_user('foo')
with patch_open() as (_open, _file):
_file.read.return_value = 'mypubkey'
result = utils.public_ssh_key('foo')
self.assertEqual(result, 'mypubkey')
def test_import_authorized_keys_missing_data(self):
self.relation_get.return_value = None
with patch_open() as (_open, _file):
utils.import_authorized_keys(user='foo')
self.assertFalse(_open.called)
@patch('pwd.getpwnam')
def _test_import_authorized_keys_base(self, getpwnam, prefix=None,
auth_key_path='/home/foo/.ssh/'
'authorized_keys'):
getpwnam.return_value = self.fake_user('foo')
self.relation_get.side_effect = [
3, # relation_get('known_hosts_max_index')
'k_h_0', # relation_get_('known_hosts_0')
'k_h_1', # relation_get_('known_hosts_1')
'k_h_2', # relation_get_('known_hosts_2')
3, # relation_get('authorized_keys_max_index')
'auth_0', # relation_get('authorized_keys_0')
'auth_1', # relation_get('authorized_keys_1')
'auth_2', # relation_get('authorized_keys_2')
]
ex_open = [
call('/home/foo/.ssh/known_hosts', 'wt'),
call(auth_key_path, 'wt')
]
ex_write = [
call('k_h_0\n'),
call('k_h_1\n'),
call('k_h_2\n'),
call('auth_0\n'),
call('auth_1\n'),
call('auth_2\n')
]
with patch_open() as (_open, _file):
utils.import_authorized_keys(user='foo', prefix=prefix)
self.assertEqual(ex_open, _open.call_args_list)
self.assertEqual(ex_write, _file.write.call_args_list)
authkey_root = 'authorized_keys_'
known_hosts_root = 'known_hosts_'
if prefix:
authkey_root = prefix + '_authorized_keys_'
known_hosts_root = prefix + '_known_hosts_'
expected_relations = [
call(known_hosts_root + 'max_index'),
call(known_hosts_root + '0'),
call(known_hosts_root + '1'),
call(known_hosts_root + '2'),
call(authkey_root + 'max_index'),
call(authkey_root + '0'),
call(authkey_root + '1'),
call(authkey_root + '2')
]
self.assertEqual(sorted(self.relation_get.call_args_list),
sorted(expected_relations))
def test_import_authorized_keys_noprefix(self):
self._test_import_authorized_keys_base()
def test_import_authorized_keys_prefix(self):
self._test_import_authorized_keys_base(prefix='bar')
def test_import_authorized_keys_authkeypath(self):
nonstandard_path = '/etc/ssh/user-authorized-keys/{username}'
self.test_config.set('authorized-keys-path', nonstandard_path)
self._test_import_authorized_keys_base(
auth_key_path='/etc/ssh/user-authorized-keys/foo')
@patch('subprocess.check_call')
def test_import_keystone_cert_missing_data(self, check_call):
self.relation_get.return_value = None
with patch_open() as (_open, _file):
utils.import_keystone_ca_cert()
self.assertFalse(_open.called)
self.assertFalse(check_call.called)
@patch.object(utils, 'check_call')
def test_import_keystone_cert(self, check_call):
self.relation_get.return_value = 'Zm9vX2NlcnQK'
with patch_open() as (_open, _file):
utils.import_keystone_ca_cert()
_open.assert_called_with(utils.CA_CERT_PATH, 'wb')
_file.write.assert_called_with(b'foo_cert\n')
check_call.assert_called_with(['update-ca-certificates'])
@patch.object(utils, 'ceph_config_file')
@patch('charmhelpers.contrib.openstack.templating.OSConfigRenderer')
@patch.object(utils, 'resource_map')
def test_register_configs(self, resource_map, renderer,
mock_ceph_config_file):
self.os_release.return_value = 'havana'
fake_renderer = MagicMock()
fake_renderer.register = MagicMock()
renderer.return_value = fake_renderer
ctxt1 = MagicMock()
ctxt2 = MagicMock()
rsc_map = {
'/etc/nova/nova.conf': {
'services': ['nova-compute'],
'contexts': [ctxt1],
},
'/etc/nova/nova-compute.conf': {
'services': ['nova-compute'],
'contexts': [ctxt2],
},
}
resource_map.return_value = rsc_map
with tempfile.NamedTemporaryFile() as tmpfile:
mock_ceph_config_file.return_value = tmpfile.name
utils.register_configs()
renderer.assert_called_with(
openstack_release='havana', templates_dir='templates/')
ex_reg = [
call('/etc/nova/nova.conf', [ctxt1]),
call('/etc/nova/nova-compute.conf', [ctxt2]),
]
fake_renderer.register.assert_has_calls(ex_reg, any_order=True)
@patch.object(utils, 'check_call')
def test_enable_shell(self, _check_call):
utils.enable_shell('dummy')
_check_call.assert_called_with(['usermod', '-s', '/bin/bash', 'dummy'])
@patch.object(utils, 'check_call')
def test_disable_shell(self, _check_call):
utils.disable_shell('dummy')
_check_call.assert_called_with(['usermod', '-s', '/bin/false',
'dummy'])
@patch.object(utils, 'check_call')
def test_configure_subuid(self, _check_call):
utils.configure_subuid('dummy')
_check_call.assert_called_with(['usermod', '-v', '100000-200000',
'-w', '100000-200000', 'dummy'])
@patch.object(utils, 'check_call')
@patch.object(utils, 'check_output')
def test_create_libvirt_key(self, _check_output, _check_call):
key = 'AQCR2dRUaFQSOxAAC5fr79sLL3d7wVvpbbRFMg=='
self.test_config.set('virt-type', 'kvm')
utils.create_libvirt_secret(utils.CEPH_SECRET,
compute_context.CEPH_SECRET_UUID, key)
_check_output.assert_called_with(['virsh', '-c',
utils.LIBVIRT_URIS['kvm'],
'secret-list'])
_check_call.assert_called_with(['virsh', '-c',
utils.LIBVIRT_URIS['kvm'],
'secret-set-value', '--secret',
compute_context.CEPH_SECRET_UUID,
'--base64', key])
@patch.object(utils, 'check_call')
@patch.object(utils, 'check_output')
def test_create_libvirt_key_existing(self, _check_output, _check_call):
key = 'AQCR2dRUaFQSOxAAC5fr79sLL3d7wVvpbbRFMg=='
old_key = 'AQCR2dRUaFQSOxAAC5fr79sLL3d7wVvpbbRFMg==\n'
self.test_config.set('virt-type', 'kvm')
_check_output.side_effect = [
compute_context.CEPH_SECRET_UUID.encode(),
old_key.encode()]
utils.create_libvirt_secret(utils.CEPH_SECRET,
compute_context.CEPH_SECRET_UUID, key)
expected = [call(['virsh', '-c',
utils.LIBVIRT_URIS['kvm'], 'secret-list']),
call(['virsh', '-c',
utils.LIBVIRT_URIS['kvm'], 'secret-get-value',
compute_context.CEPH_SECRET_UUID])]
_check_output.assert_has_calls(expected)
self.assertFalse(_check_call.called)
@patch.object(utils, 'check_call')
@patch.object(utils, 'check_output')
def test_create_libvirt_key_stale(self, _check_output, _check_call):
key = 'AQCR2dRUaFQSOxAAC5fr79sLL3d7wVvpbbRFMg=='
old_key = 'CCCCCdRUaFQSOxAAC5fr79sLL3d7wVvpbbRFMg=='
self.test_config.set('virt-type', 'kvm')
_check_output.side_effect = [
compute_context.CEPH_SECRET_UUID.encode(),
old_key.encode()]
utils.create_libvirt_secret(utils.CEPH_SECRET,
compute_context.CEPH_SECRET_UUID, key)
expected = [call(['virsh', '-c',
utils.LIBVIRT_URIS['kvm'], 'secret-list']),
call(['virsh', '-c',
utils.LIBVIRT_URIS['kvm'], 'secret-get-value',
compute_context.CEPH_SECRET_UUID])]
_check_output.assert_has_calls(expected)
_check_call.assert_any_call(['virsh', '-c',
utils.LIBVIRT_URIS['kvm'],
'secret-set-value', '--secret',
compute_context.CEPH_SECRET_UUID,
'--base64', key])
@patch.object(utils, 'lxc_list')
@patch.object(utils, 'configure_subuid')
def test_configure_lxd_vivid(self, _configure_subuid, _lxc_list):
self.lsb_release.return_value = {
'DISTRIB_CODENAME': 'vivid'
}
utils.configure_lxd('nova')
_configure_subuid.assert_called_with('nova')
_lxc_list.assert_called_with('nova')
@patch.object(utils, 'lxc_list')
@patch.object(utils, 'configure_subuid')
def test_configure_lxd_pre_vivid(self, _configure_subuid, _lxc_list):
self.lsb_release.return_value = {
'DISTRIB_CODENAME': 'trusty'
}
with self.assertRaises(Exception):
utils.configure_lxd('nova')
self.assertFalse(_configure_subuid.called)
@patch('psutil.virtual_memory')
@patch('subprocess.check_call')
@patch('subprocess.call')
def test_install_hugepages(self, _call, _check_call, _virt_mem):
class mem(object):
def __init__(self):
self.total = 10000000 * 1024
self.test_config.set('hugepages', '10%')
_virt_mem.side_effect = mem
_call.return_value = 1
utils.install_hugepages()
self.hugepage_support.assert_called_with(
'nova',
mnt_point='/run/hugepages/kvm',
group='root',
nr_hugepages=488,
mount=False,
set_shmmax=True,
)
check_call_calls = [
call('/etc/init.d/qemu-hugefsdir'),
call(['update-rc.d', 'qemu-hugefsdir', 'defaults']),
]
_check_call.assert_has_calls(check_call_calls)
self.Fstab.remove_by_mountpoint.assert_called_with(
'/run/hugepages/kvm')
@patch('psutil.virtual_memory')
@patch('subprocess.check_call')
@patch('subprocess.call')
def test_install_hugepages_explicit_size(self, _call, _check_call,
_virt_mem):
self.test_config.set('hugepages', '2048')
utils.install_hugepages()
self.hugepage_support.assert_called_with(
'nova',
mnt_point='/run/hugepages/kvm',
group='root',
nr_hugepages=2048,
mount=False,
set_shmmax=True,
)
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()
self.os_application_version_set.assert_called_with(
utils.VERSION_PACKAGE
)
@patch.object(utils, 'REQUIRED_INTERFACES')
@patch.object(utils, 'services')
@patch.object(utils, 'make_assess_status_func')
@patch.object(utils, 'get_optional_relations')
def test_assess_status_func(self,
get_optional_relations,
make_assess_status_func,
services,
REQUIRED_INTERFACES):
services.return_value = 's1'
REQUIRED_INTERFACES.copy.return_value = {'test-interface': True}
get_optional_relations.return_value = {'optional': False}
test_interfaces = {
'test-interface': True,
'optional': False,
}
utils.assess_status_func('test-config')
# ports=None whilst port checks are disabled.
make_assess_status_func.assert_called_once_with(
'test-config', test_interfaces, services='s1', ports=None)
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')
def test_pause_resume_helper(self, services):
f = MagicMock()
services.return_value = 's1'
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')
# ports=None whilst port checks are disabled.
f.assert_called_once_with('assessor', services='s1', ports=None)
@patch.object(utils, 'check_call')
@patch.object(utils, 'check_output')
def test_destroy_libvirt_network(self, mock_check_output, mock_check_call):
mock_check_output.return_value = VIRSH_NET_LIST.encode()
utils.destroy_libvirt_network('default')
cmd = ['virsh', 'net-destroy', 'default']
mock_check_call.assert_has_calls([call(cmd)])
@patch.object(utils, 'check_call')
@patch.object(utils, 'check_output')
def test_destroy_libvirt_network_no_exist(self, mock_check_output,
mock_check_call):
mock_check_output.return_value = VIRSH_NET_LIST.encode()
utils.destroy_libvirt_network('defaultX')
self.assertFalse(mock_check_call.called)
@patch.object(utils, 'check_call')
@patch.object(utils, 'check_output')
def test_destroy_libvirt_network_no_virsh(self, mock_check_output,
mock_check_call):
mock_check_output.side_effect = OSError(2, 'No such file')
utils.destroy_libvirt_network('default')
@patch.object(utils, 'check_call')
@patch.object(utils, 'check_output')
def test_destroy_libvirt_network_no_virsh_unknown_error(self,
mock_check_output,
mock_check_call):
mock_check_output.side_effect = OSError(100, 'Break things')
with self.assertRaises(OSError):
utils.destroy_libvirt_network('default')
def test_libvirt_daemon_yakkety(self):
self.lsb_release.return_value = {
'DISTRIB_CODENAME': 'yakkety'
}
self.assertEqual(utils.libvirt_daemon(), utils.LIBVIRTD_DAEMON)
def test_libvirt_daemon_preyakkety(self):
self.os_release.return_value = 'diablo'
self.lsb_release.return_value = {
'DISTRIB_CODENAME': 'xenial'
}
self.assertEqual(utils.libvirt_daemon(), utils.LIBVIRT_BIN_DAEMON)
@patch.object(utils, 'os')
def test_determine_block_device(self, mock_os):
self.test_config.set('ephemeral-device', '/dev/sdd')
mock_os.path.exists.return_value = True
self.assertEqual(utils.determine_block_device(), '/dev/sdd')
self.config.assert_called_with('ephemeral-device')
def test_determine_block_device_storage(self):
_test_devices = {
'a': '/dev/bcache0'
}
self.storage_list.side_effect = _test_devices.keys()
self.storage_get.side_effect = lambda _, key: _test_devices.get(key)
self.assertEqual(utils.determine_block_device(), '/dev/bcache0')
self.config.assert_called_with('ephemeral-device')
self.storage_get.assert_called_with('location', 'a')
self.storage_list.assert_called_with('ephemeral-device')
def test_determine_block_device_none(self):
self.storage_list.return_value = []
self.assertEqual(utils.determine_block_device(), None)
self.config.assert_called_with('ephemeral-device')
self.storage_list.assert_called_with('ephemeral-device')
@patch.object(utils, 'uuid')
@patch.object(utils, 'determine_block_device')
def test_configure_local_ephemeral_storage_encrypted(
self,
determine_block_device,
uuid):
determine_block_device.return_value = '/dev/sdb'
uuid.uuid4.return_value = 'test'
mock_context = MagicMock()
mock_context.complete = True
mock_context.return_value = 'test_context'
self.test_config.set('encrypt', True)
self.vaultlocker.VaultKVContext.return_value = mock_context
self.is_block_device.return_value = True
self.is_device_mounted.return_value = False
utils.configure_local_ephemeral_storage()
self.mkfs_xfs.assert_called_with(
'/dev/mapper/crypt-test',
force=True
)
self.check_call.assert_has_calls([
call(['vaultlocker', 'encrypt',
'--uuid', 'test', '/dev/sdb']),
call(['chown', '-R', 'nova:nova',
'/var/lib/nova/instances']),
call(['chmod', '-R', '0755',
'/var/lib/nova/instances'])
])
self.mount.assert_called_with(
'/dev/mapper/crypt-test',
'/var/lib/nova/instances',
filesystem='xfs')
self.fstab_add.assert_called_with(
'/dev/mapper/crypt-test',
'/var/lib/nova/instances',
'xfs',
options='defaults,nofail,'
'x-systemd.requires=vaultlocker-decrypt@test.service,'
'comment=vaultlocker'
)
self.assertTrue(self.test_kv.get('storage-configured'))
self.vaultlocker.write_vaultlocker_conf.assert_called_with(
'test_context',
priority=80
)
@patch.object(utils, 'uuid')
@patch.object(utils, 'determine_block_device')
def test_configure_local_ephemeral_storage(self,
determine_block_device,
uuid):
determine_block_device.return_value = '/dev/sdb'
uuid.uuid4.return_value = 'test'
mock_context = MagicMock()
mock_context.complete = False
mock_context.return_value = {}
self.test_config.set('encrypt', False)
self.vaultlocker.VaultKVContext.return_value = mock_context
self.is_block_device.return_value = True
self.is_device_mounted.return_value = False
utils.configure_local_ephemeral_storage()
self.mkfs_xfs.assert_called_with(
'/dev/sdb',
force=True
)
self.check_call.assert_has_calls([
call(['chown', '-R', 'nova:nova',
'/var/lib/nova/instances']),
call(['chmod', '-R', '0755',
'/var/lib/nova/instances'])
])
self.mount.assert_called_with(
'/dev/sdb',
'/var/lib/nova/instances',
filesystem='xfs')
self.fstab_add.assert_called_with(
'/dev/sdb',
'/var/lib/nova/instances',
'xfs',
options=None
)
self.assertTrue(self.test_kv.get('storage-configured'))
self.vaultlocker.write_vaultlocker_conf.assert_not_called()
def test_configure_local_ephemeral_storage_done(self):
self.test_kv.set('storage-configured', True)
mock_context = MagicMock()
mock_context.complete = True
mock_context.return_value = 'test_context'
self.test_config.set('encrypt', True)
self.vaultlocker.VaultKVContext.return_value = mock_context
utils.configure_local_ephemeral_storage()
# NOTE: vaultlocker conf should always be re-written to
# pickup any changes to secret_id over time.
self.vaultlocker.write_vaultlocker_conf.assert_called_with(
'test_context',
priority=80
)
self.is_block_device.assert_not_called()