
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
955 lines
37 KiB
Python
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()
|