388a131c3d
Most of the parts for oslo-incubator have moved to oslo libraries. Cinder only had imageutils.py and _i18n.py remaining in cinder/openstack/common. I was to able remove both of these remaining incubator components. oslo_utils now has imageutils available. I moved cinder/image/image_utils.py to use the new imageutils from oslo_utils and updated all the other places that were using the old imageutils import. _i18n was only being used in scheduler/base_filter.py which appears to have been accidentally left when the scheduler was previously pulled out of incubator. I fixed this oversight. Finally, this patch deletes cinder/openstack/common cinder/openstack and the openstack-common.conf file. Change-Id: I8fe0b5d6c278715620f93f682289df7ae60dbeba Closes-bug: 1517861
368 lines
16 KiB
Python
368 lines
16 KiB
Python
# Copyright (c) 2015 Scality
|
|
# All Rights Reserved.
|
|
#
|
|
# 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.
|
|
|
|
"""
|
|
Unit tests for the Scality SOFS Volume Driver.
|
|
"""
|
|
import errno
|
|
import os
|
|
|
|
import mock
|
|
from oslo_utils import imageutils
|
|
from six.moves import urllib
|
|
|
|
from cinder import context
|
|
from cinder import exception
|
|
from cinder import test
|
|
from cinder.volume import configuration as conf
|
|
import cinder.volume.drivers.scality as driver
|
|
|
|
_FAKE_VOLUME = {'name': 'volume-a79d463e-1fd5-11e5-a6ff-5b81bfee8544',
|
|
'id': 'a79d463e-1fd5-11e5-a6ff-5b81bfee8544',
|
|
'provider_location': 'fake_share'}
|
|
_FAKE_SNAPSHOT = {'id': 'ae3d6da2-1fd5-11e5-967f-1b8cf3b401ab',
|
|
'volume': _FAKE_VOLUME,
|
|
'status': 'available',
|
|
'provider_location': None,
|
|
'volume_size': 1,
|
|
'name': 'snapshot-ae3d6da2-1fd5-11e5-967f-1b8cf3b401ab'}
|
|
_FAKE_BACKUP = {'id': '914849d2-2585-11e5-be54-d70ca0c343d6',
|
|
'volume_id': _FAKE_VOLUME['id']}
|
|
|
|
_FAKE_MNT_POINT = '/tmp'
|
|
_FAKE_SOFS_CONFIG = '/etc/sfused.conf'
|
|
_FAKE_VOLUME_DIR = 'cinder/volumes'
|
|
_FAKE_VOL_BASEDIR = os.path.join(_FAKE_MNT_POINT, _FAKE_VOLUME_DIR, '00')
|
|
_FAKE_VOL_PATH = os.path.join(_FAKE_VOL_BASEDIR, _FAKE_VOLUME['name'])
|
|
_FAKE_SNAP_PATH = os.path.join(_FAKE_VOL_BASEDIR, _FAKE_SNAPSHOT['name'])
|
|
|
|
_FAKE_MOUNTS_TABLE = [['tmpfs /dev/shm\n'],
|
|
['fuse ' + _FAKE_MNT_POINT + '\n']]
|
|
|
|
|
|
class ScalityDriverTestCase(test.TestCase):
|
|
"""Test case for the Scality driver."""
|
|
|
|
def setUp(self):
|
|
super(ScalityDriverTestCase, self).setUp()
|
|
|
|
self.cfg = mock.Mock(spec=conf.Configuration)
|
|
self.cfg.scality_sofs_mount_point = _FAKE_MNT_POINT
|
|
self.cfg.scality_sofs_config = _FAKE_SOFS_CONFIG
|
|
self.cfg.scality_sofs_volume_dir = _FAKE_VOLUME_DIR
|
|
|
|
self.drv = driver.ScalityDriver(configuration=self.cfg)
|
|
self.drv.db = mock.Mock()
|
|
|
|
@mock.patch.object(driver.urllib.request, 'urlopen')
|
|
@mock.patch('os.access')
|
|
def test_check_for_setup_error(self, mock_os_access, mock_urlopen):
|
|
self.drv.check_for_setup_error()
|
|
|
|
mock_urlopen.assert_called_once_with('file://%s' % _FAKE_SOFS_CONFIG,
|
|
timeout=5)
|
|
mock_os_access.assert_called_once_with('/sbin/mount.sofs', os.X_OK)
|
|
|
|
def test_check_for_setup_error_with_no_sofs_config(self):
|
|
self.cfg.scality_sofs_config = ''
|
|
|
|
self.drv = driver.ScalityDriver(configuration=self.cfg)
|
|
|
|
self.assertRaises(exception.VolumeBackendAPIException,
|
|
self.drv.check_for_setup_error)
|
|
exec_patcher = mock.patch.object(self.drv, '_execute',
|
|
mock.MagicMock())
|
|
exec_patcher.start()
|
|
self.addCleanup(exec_patcher.stop)
|
|
|
|
@mock.patch.object(driver.urllib.request, 'urlopen')
|
|
def test_check_for_setup_error_with_urlerror(self, mock_urlopen):
|
|
# Add a Unicode char to be sure that the exception is properly
|
|
# handled even if it contains Unicode chars
|
|
mock_urlopen.side_effect = urllib.error.URLError(u'\u9535')
|
|
self.assertRaises(exception.VolumeBackendAPIException,
|
|
self.drv.check_for_setup_error)
|
|
|
|
@mock.patch.object(driver.urllib.request, 'urlopen')
|
|
def test_check_for_setup_error_with_httperror(self, mock_urlopen):
|
|
mock_urlopen.side_effect = urllib.error.HTTPError(*[None] * 5)
|
|
self.assertRaises(exception.VolumeBackendAPIException,
|
|
self.drv.check_for_setup_error)
|
|
|
|
@mock.patch.object(driver.urllib.request, 'urlopen', mock.Mock())
|
|
@mock.patch('os.access')
|
|
def test_check_for_setup_error_with_no_mountsofs(self, mock_os_access):
|
|
mock_os_access.return_value = False
|
|
self.assertRaises(exception.VolumeBackendAPIException,
|
|
self.drv.check_for_setup_error)
|
|
mock_os_access.assert_called_once_with('/sbin/mount.sofs', os.X_OK)
|
|
|
|
def test_load_shares_config(self):
|
|
self.assertEqual({}, self.drv.shares)
|
|
self.drv._load_shares_config()
|
|
self.assertEqual({_FAKE_VOLUME_DIR: None}, self.drv.shares)
|
|
|
|
def test_get_mount_point_for_share(self):
|
|
self.assertEqual(_FAKE_VOL_BASEDIR,
|
|
self.drv._get_mount_point_for_share())
|
|
|
|
@mock.patch("cinder.volume.utils.read_proc_mounts")
|
|
@mock.patch("oslo_concurrency.processutils.execute")
|
|
def test_ensure_share_mounted_when_mount_failed(self, mock_execute,
|
|
mock_read_proc_mounts):
|
|
mock_read_proc_mounts.return_value = ['tmpfs /dev/shm\n']
|
|
self.assertRaises(exception.VolumeBackendAPIException,
|
|
self.drv._ensure_share_mounted)
|
|
self.assertEqual(2, mock_read_proc_mounts.call_count)
|
|
self.assertEqual(1, mock_execute.call_count)
|
|
|
|
@mock.patch("cinder.volume.utils.read_proc_mounts")
|
|
@mock.patch("oslo_concurrency.processutils.execute")
|
|
@mock.patch("oslo_utils.fileutils.ensure_tree")
|
|
@mock.patch("os.symlink")
|
|
def test_ensure_shares_mounted(self, mock_symlink, mock_ensure_tree,
|
|
mock_execute, mock_read_proc_mounts):
|
|
self.assertEqual([], self.drv._mounted_shares)
|
|
|
|
mock_read_proc_mounts.side_effect = _FAKE_MOUNTS_TABLE
|
|
|
|
self.drv._ensure_shares_mounted()
|
|
|
|
self.assertEqual([_FAKE_VOLUME_DIR], self.drv._mounted_shares)
|
|
self.assertEqual(2, mock_read_proc_mounts.call_count)
|
|
mock_symlink.assert_called_once_with('.', _FAKE_VOL_BASEDIR)
|
|
self.assertEqual(2, mock_ensure_tree.call_count)
|
|
self.assertEqual(1, mock_execute.call_count)
|
|
expected_args = ('mount', '-t', 'sofs', _FAKE_SOFS_CONFIG,
|
|
_FAKE_MNT_POINT)
|
|
self.assertEqual(expected_args, mock_execute.call_args[0])
|
|
|
|
@mock.patch("cinder.volume.utils.read_proc_mounts")
|
|
@mock.patch("oslo_concurrency.processutils.execute")
|
|
@mock.patch("oslo_utils.fileutils.ensure_tree", mock.Mock())
|
|
@mock.patch("os.symlink", mock.Mock())
|
|
def test_ensure_shares_mounted_when_sofs_mounted(self, mock_execute,
|
|
mock_read_proc_mounts):
|
|
mock_read_proc_mounts.return_value = _FAKE_MOUNTS_TABLE[1]
|
|
|
|
self.drv._ensure_shares_mounted()
|
|
|
|
# Because SOFS is mounted from the beginning, we shouldn't read
|
|
# /proc/mounts more than once.
|
|
mock_read_proc_mounts.assert_called_once_with()
|
|
self.assertFalse(mock_execute.called)
|
|
|
|
def test_find_share_when_no_shares_mounted(self):
|
|
self.assertRaises(exception.RemoteFSNoSharesMounted,
|
|
self.drv._find_share, 'ignored')
|
|
|
|
@mock.patch("cinder.volume.utils.read_proc_mounts")
|
|
@mock.patch("oslo_concurrency.processutils.execute")
|
|
@mock.patch("oslo_utils.fileutils.ensure_tree")
|
|
@mock.patch("os.symlink")
|
|
def test_find_share(self, mock_symlink, mock_ensure_tree, mock_execute,
|
|
mock_read_proc_mounts):
|
|
mock_read_proc_mounts.side_effect = _FAKE_MOUNTS_TABLE
|
|
|
|
self.drv._ensure_shares_mounted()
|
|
|
|
self.assertEqual(_FAKE_VOLUME_DIR, self.drv._find_share('ignored'))
|
|
self.assertEqual(2, mock_read_proc_mounts.call_count)
|
|
self.assertEqual(1, mock_execute.call_count)
|
|
|
|
expected_args = ('mount', '-t', 'sofs', _FAKE_SOFS_CONFIG,
|
|
_FAKE_MNT_POINT)
|
|
self.assertEqual(expected_args, mock_execute.call_args[0])
|
|
|
|
mock_symlink.assert_called_once_with('.', _FAKE_VOL_BASEDIR)
|
|
|
|
self.assertEqual(mock_ensure_tree.call_args_list,
|
|
[mock.call(_FAKE_MNT_POINT),
|
|
mock.call(os.path.join(_FAKE_MNT_POINT,
|
|
_FAKE_VOLUME_DIR))])
|
|
|
|
def test_get_volume_stats(self):
|
|
with mock.patch.object(self.cfg, 'safe_get') as mock_safe_get:
|
|
mock_safe_get.return_value = 'fake_backend_name'
|
|
stats = self.drv.get_volume_stats()
|
|
self.assertEqual(self.drv.VERSION, stats['driver_version'])
|
|
self.assertEqual(mock_safe_get.return_value,
|
|
stats['volume_backend_name'])
|
|
mock_safe_get.assert_called_once_with('volume_backend_name')
|
|
|
|
@mock.patch("cinder.image.image_utils.qemu_img_info")
|
|
def test_initialize_connection(self, mock_qemu_img_info):
|
|
info = imageutils.QemuImgInfo()
|
|
info.file_format = 'raw'
|
|
info.image = _FAKE_VOLUME['name']
|
|
mock_qemu_img_info.return_value = info
|
|
|
|
with mock.patch.object(self.drv, 'get_active_image_from_info') as \
|
|
mock_get_active_image_from_info:
|
|
|
|
mock_get_active_image_from_info.return_value = _FAKE_VOLUME['name']
|
|
conn_info = self.drv.initialize_connection(_FAKE_VOLUME, None)
|
|
|
|
expected_conn_info = {
|
|
'driver_volume_type': driver.ScalityDriver.driver_volume_type,
|
|
'mount_point_base': _FAKE_MNT_POINT,
|
|
'data': {
|
|
'export': _FAKE_VOLUME['provider_location'],
|
|
'name': _FAKE_VOLUME['name'],
|
|
'sofs_path': 'cinder/volumes/00/' + _FAKE_VOLUME['name'],
|
|
'format': 'raw'
|
|
}
|
|
}
|
|
self.assertEqual(expected_conn_info, conn_info)
|
|
mock_get_active_image_from_info.assert_called_once_with(_FAKE_VOLUME)
|
|
mock_qemu_img_info.assert_called_once_with(_FAKE_VOL_PATH)
|
|
|
|
@mock.patch("cinder.image.image_utils.resize_image")
|
|
@mock.patch("cinder.image.image_utils.qemu_img_info")
|
|
def test_extend_volume(self, mock_qemu_img_info, mock_resize_image):
|
|
info = imageutils.QemuImgInfo()
|
|
info.file_format = 'raw'
|
|
mock_qemu_img_info.return_value = info
|
|
|
|
self.drv.extend_volume(_FAKE_VOLUME, 2)
|
|
|
|
mock_qemu_img_info.assert_called_once_with(_FAKE_VOL_PATH)
|
|
|
|
mock_resize_image.assert_called_once_with(_FAKE_VOL_PATH, 2)
|
|
|
|
@mock.patch("cinder.image.image_utils.qemu_img_info")
|
|
def test_extend_volume_with_invalid_format(self, mock_qemu_img_info):
|
|
info = imageutils.QemuImgInfo()
|
|
info.file_format = 'vmdk'
|
|
mock_qemu_img_info.return_value = info
|
|
|
|
self.assertRaises(exception.InvalidVolume,
|
|
self.drv.extend_volume, _FAKE_VOLUME, 2)
|
|
|
|
@mock.patch("cinder.image.image_utils.resize_image")
|
|
@mock.patch("cinder.image.image_utils.convert_image")
|
|
def test_copy_volume_from_snapshot_with_ioerror(self, mock_convert_image,
|
|
mock_resize_image):
|
|
with mock.patch.object(self.drv, '_read_info_file') as \
|
|
mock_read_info_file, \
|
|
mock.patch.object(self.drv, '_set_rw_permissions_for_all') as \
|
|
mock_set_rw_permissions:
|
|
mock_read_info_file.side_effect = IOError(errno.ENOENT, '')
|
|
self.drv._copy_volume_from_snapshot(_FAKE_SNAPSHOT,
|
|
_FAKE_VOLUME, 1)
|
|
|
|
mock_read_info_file.assert_called_once_with("%s.info" % _FAKE_VOL_PATH)
|
|
mock_convert_image.assert_called_once_with(_FAKE_SNAP_PATH,
|
|
_FAKE_VOL_PATH, 'raw',
|
|
run_as_root=True)
|
|
mock_set_rw_permissions.assert_called_once_with(_FAKE_VOL_PATH)
|
|
mock_resize_image.assert_called_once_with(_FAKE_VOL_PATH, 1)
|
|
|
|
@mock.patch("cinder.image.image_utils.resize_image")
|
|
@mock.patch("cinder.image.image_utils.convert_image")
|
|
@mock.patch("cinder.image.image_utils.qemu_img_info")
|
|
def test_copy_volume_from_snapshot(self, mock_qemu_img_info,
|
|
mock_convert_image, mock_resize_image):
|
|
|
|
new_volume = {'name': 'volume-3fa63b02-1fe5-11e5-b492-abf97a8fb23b',
|
|
'id': '3fa63b02-1fe5-11e5-b492-abf97a8fb23b',
|
|
'provider_location': 'fake_share'}
|
|
new_vol_path = os.path.join(_FAKE_VOL_BASEDIR, new_volume['name'])
|
|
|
|
info = imageutils.QemuImgInfo()
|
|
info.file_format = 'raw'
|
|
info.backing_file = _FAKE_VOL_PATH
|
|
mock_qemu_img_info.return_value = info
|
|
|
|
with mock.patch.object(self.drv, '_read_info_file') as \
|
|
mock_read_info_file, \
|
|
mock.patch.object(self.drv, '_set_rw_permissions_for_all') as \
|
|
mock_set_rw_permissions:
|
|
self.drv._copy_volume_from_snapshot(_FAKE_SNAPSHOT,
|
|
new_volume, 1)
|
|
|
|
mock_read_info_file.assert_called_once_with("%s.info" % _FAKE_VOL_PATH)
|
|
mock_convert_image.assert_called_once_with(_FAKE_VOL_PATH,
|
|
new_vol_path, 'raw',
|
|
run_as_root=True)
|
|
mock_set_rw_permissions.assert_called_once_with(new_vol_path)
|
|
mock_resize_image.assert_called_once_with(new_vol_path, 1)
|
|
|
|
@mock.patch("cinder.image.image_utils.qemu_img_info")
|
|
@mock.patch("cinder.utils.temporary_chown")
|
|
@mock.patch("six.moves.builtins.open")
|
|
def test_backup_volume(self, mock_open, mock_temporary_chown,
|
|
mock_qemu_img_info):
|
|
"""Backup a volume with no snapshots."""
|
|
|
|
info = imageutils.QemuImgInfo()
|
|
info.file_format = 'raw'
|
|
mock_qemu_img_info.return_value = info
|
|
|
|
backup = {'volume_id': _FAKE_VOLUME['id']}
|
|
mock_backup_service = mock.MagicMock()
|
|
self.drv.db.volume_get.return_value = _FAKE_VOLUME
|
|
|
|
self.drv.backup_volume(context, backup, mock_backup_service)
|
|
|
|
mock_qemu_img_info.assert_called_once_with(_FAKE_VOL_PATH)
|
|
mock_temporary_chown.assert_called_once_with(_FAKE_VOL_PATH)
|
|
mock_open.assert_called_once_with(_FAKE_VOL_PATH)
|
|
mock_backup_service.backup.assert_called_once_with(
|
|
backup, mock_open().__enter__())
|
|
|
|
@mock.patch("cinder.image.image_utils.qemu_img_info")
|
|
def test_backup_volume_with_non_raw_volume(self, mock_qemu_img_info):
|
|
|
|
info = imageutils.QemuImgInfo()
|
|
info.file_format = 'qcow2'
|
|
mock_qemu_img_info.return_value = info
|
|
|
|
self.drv.db.volume_get.return_value = _FAKE_VOLUME
|
|
|
|
self.assertRaises(exception.InvalidVolume, self.drv.backup_volume,
|
|
context, _FAKE_BACKUP, mock.MagicMock())
|
|
|
|
mock_qemu_img_info.assert_called_once_with(_FAKE_VOL_PATH)
|
|
|
|
@mock.patch("cinder.image.image_utils.qemu_img_info")
|
|
def test_backup_volume_with_backing_file(self, mock_qemu_img_info):
|
|
|
|
info = imageutils.QemuImgInfo()
|
|
info.file_format = 'raw'
|
|
info.backing_file = 'fake.img'
|
|
mock_qemu_img_info.return_value = info
|
|
|
|
backup = {'volume_id': _FAKE_VOLUME['id']}
|
|
self.drv.db.volume_get.return_value = _FAKE_VOLUME
|
|
|
|
self.assertRaises(exception.InvalidVolume, self.drv.backup_volume,
|
|
context, backup, mock.MagicMock())
|
|
|
|
mock_qemu_img_info.assert_called_once_with(_FAKE_VOL_PATH)
|
|
|
|
@mock.patch("cinder.utils.temporary_chown")
|
|
@mock.patch("six.moves.builtins.open")
|
|
def test_restore_bakup(self, mock_open, mock_temporary_chown):
|
|
mock_backup_service = mock.MagicMock()
|
|
|
|
self.drv.restore_backup(context, _FAKE_BACKUP, _FAKE_VOLUME,
|
|
mock_backup_service)
|
|
|
|
mock_temporary_chown.assert_called_once_with(_FAKE_VOL_PATH)
|
|
mock_open.assert_called_once_with(_FAKE_VOL_PATH, 'wb')
|
|
mock_backup_service.restore.assert_called_once_with(
|
|
_FAKE_BACKUP, _FAKE_VOLUME['id'], mock_open().__enter__())
|