Remove Scality backend driver
The Scality volume driver was marked as not supported in Newton. There is still no CI running for this driver so following our non-compliance policy [1] it is now removed. [1] https://wiki.openstack.org/wiki/Cinder/tested-3rdParty-drivers#Non-Compliance_Policy Change-Id: I9853b7c7845037a919ff226fd4b988398178831c
This commit is contained in:
parent
cf12eedda4
commit
a931f9db79
@ -167,7 +167,6 @@ from cinder.volume.drivers import remotefs as cinder_volume_drivers_remotefs
|
||||
from cinder.volume.drivers.san.hp import hpmsa_common as \
|
||||
cinder_volume_drivers_san_hp_hpmsacommon
|
||||
from cinder.volume.drivers.san import san as cinder_volume_drivers_san_san
|
||||
from cinder.volume.drivers import scality as cinder_volume_drivers_scality
|
||||
from cinder.volume.drivers import sheepdog as cinder_volume_drivers_sheepdog
|
||||
from cinder.volume.drivers import smbfs as cinder_volume_drivers_smbfs
|
||||
from cinder.volume.drivers import solidfire as cinder_volume_drivers_solidfire
|
||||
@ -359,7 +358,6 @@ def list_opts():
|
||||
cinder_volume_drivers_san_hp_hpmsacommon.common_opts,
|
||||
cinder_volume_drivers_san_hp_hpmsacommon.iscsi_opts,
|
||||
cinder_volume_drivers_san_san.san_opts,
|
||||
cinder_volume_drivers_scality.volume_opts,
|
||||
cinder_volume_drivers_sheepdog.sheepdog_opts,
|
||||
cinder_volume_drivers_smbfs.volume_opts,
|
||||
cinder_volume_drivers_solidfire.sf_opts,
|
||||
|
@ -1,392 +0,0 @@
|
||||
# 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.tests.unit import fake_snapshot
|
||||
from cinder.tests.unit import fake_volume
|
||||
from cinder.volume import configuration as conf
|
||||
import cinder.volume.drivers.scality as driver
|
||||
|
||||
_FAKE_VOLUME_ID = 'a79d463e-1fd5-11e5-a6ff-5b81bfee8544'
|
||||
_FAKE_VOLUME_NAME = 'volume-a79d463e-1fd5-11e5-a6ff-5b81bfee8544'
|
||||
|
||||
_FAKE_SNAPSHOT_ID = 'ae3d6da2-1fd5-11e5-967f-1b8cf3b401ab'
|
||||
_FAKE_SNAPSHOT_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()
|
||||
|
||||
self.context = context.get_admin_context()
|
||||
|
||||
def _simple_volume(self, **kwargs):
|
||||
updates = {'display_name': _FAKE_VOLUME_NAME,
|
||||
'id': _FAKE_VOLUME_ID,
|
||||
'provider_location': 'fake_share'}
|
||||
updates.update(kwargs)
|
||||
|
||||
return fake_volume.fake_volume_obj(self.context, **updates)
|
||||
|
||||
def _simple_snapshot(self, **kwargs):
|
||||
updates = {'id': _FAKE_SNAPSHOT_ID,
|
||||
'display_name': _FAKE_SNAPSHOT_NAME,
|
||||
'status': 'available',
|
||||
'provider_location': None,
|
||||
'volume_size': 1}
|
||||
|
||||
updates.update(kwargs)
|
||||
snapshot = fake_snapshot.fake_snapshot_obj(self.context, **updates)
|
||||
volume = self._simple_volume()
|
||||
snapshot.volume = volume
|
||||
|
||||
return snapshot
|
||||
|
||||
@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
|
||||
|
||||
volume = self._simple_volume()
|
||||
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(volume, None)
|
||||
|
||||
expected_conn_info = {
|
||||
'driver_volume_type': driver.ScalityDriver.driver_volume_type,
|
||||
'mount_point_base': _FAKE_MNT_POINT,
|
||||
'data': {
|
||||
'export': volume.provider_location,
|
||||
'name': volume.name,
|
||||
'sofs_path': 'cinder/volumes/00/' + volume.name,
|
||||
'format': 'raw'
|
||||
}
|
||||
}
|
||||
self.assertEqual(expected_conn_info, conn_info)
|
||||
mock_get_active_image_from_info.assert_called_once_with(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(self._simple_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, self._simple_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(self._simple_snapshot(),
|
||||
self._simple_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 = self._simple_volume(
|
||||
display_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(self._simple_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 = self._simple_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 = self._simple_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 = self._simple_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, self._simple_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__())
|
@ -1,306 +0,0 @@
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Scality SOFS Volume Driver.
|
||||
"""
|
||||
|
||||
|
||||
import errno
|
||||
import os
|
||||
|
||||
from oslo_concurrency import lockutils
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import fileutils
|
||||
import six
|
||||
from six.moves import urllib
|
||||
|
||||
from cinder import exception
|
||||
from cinder.i18n import _, _LI
|
||||
from cinder.image import image_utils
|
||||
from cinder import interface
|
||||
from cinder import utils
|
||||
from cinder.volume.drivers import remotefs as remotefs_drv
|
||||
from cinder.volume import utils as volume_utils
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
volume_opts = [
|
||||
cfg.StrOpt('scality_sofs_config',
|
||||
help='Path or URL to Scality SOFS configuration file'),
|
||||
cfg.StrOpt('scality_sofs_mount_point',
|
||||
default='$state_path/scality',
|
||||
help='Base dir where Scality SOFS shall be mounted'),
|
||||
cfg.StrOpt('scality_sofs_volume_dir',
|
||||
default='cinder/volumes',
|
||||
help='Path from Scality SOFS root to volume dir'),
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.register_opts(volume_opts)
|
||||
|
||||
|
||||
@interface.volumedriver
|
||||
class ScalityDriver(remotefs_drv.RemoteFSSnapDriver):
|
||||
"""Scality SOFS cinder driver.
|
||||
|
||||
Creates sparse files on SOFS for hypervisors to use as block
|
||||
devices.
|
||||
"""
|
||||
|
||||
driver_volume_type = 'scality'
|
||||
driver_prefix = 'scality_sofs'
|
||||
volume_backend_name = 'Scality_SOFS'
|
||||
VERSION = '2.0.0'
|
||||
|
||||
# ThirdPartySystems wiki page
|
||||
CI_WIKI_NAME = "Scality_CI"
|
||||
|
||||
# TODO(smcginnis) Either remove this if CI requirements are met, or
|
||||
# remove this driver in the Ocata release per normal deprecation
|
||||
SUPPORTED = False
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ScalityDriver, self).__init__(*args, **kwargs)
|
||||
self.configuration.append_config_values(volume_opts)
|
||||
|
||||
self.sofs_mount_point = self.configuration.scality_sofs_mount_point
|
||||
self.sofs_config = self.configuration.scality_sofs_config
|
||||
self.sofs_rel_volume_dir = self.configuration.scality_sofs_volume_dir
|
||||
self.sofs_abs_volume_dir = os.path.join(self.sofs_mount_point,
|
||||
self.sofs_rel_volume_dir)
|
||||
|
||||
# The following config flag is used by RemoteFSDriver._do_create_volume
|
||||
# We want to use sparse file (ftruncated) without exposing this
|
||||
# as a config switch to customers.
|
||||
self.configuration.scality_sofs_sparsed_volumes = True
|
||||
|
||||
def check_for_setup_error(self):
|
||||
"""Sanity checks before attempting to mount SOFS."""
|
||||
|
||||
# config is mandatory
|
||||
if not self.sofs_config:
|
||||
msg = _("Value required for 'scality_sofs_config'")
|
||||
LOG.error(msg)
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
|
||||
# config can be a file path or a URL, check it
|
||||
config = self.sofs_config
|
||||
if urllib.parse.urlparse(self.sofs_config).scheme == '':
|
||||
# turn local path into URL
|
||||
config = 'file://%s' % self.sofs_config
|
||||
try:
|
||||
urllib.request.urlopen(config, timeout=5).close()
|
||||
except (urllib.error.URLError, urllib.error.HTTPError) as e:
|
||||
msg = _("Can't access 'scality_sofs_config'"
|
||||
": %s") % six.text_type(e)
|
||||
LOG.error(msg)
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
|
||||
# mount.sofs must be installed
|
||||
if not os.access('/sbin/mount.sofs', os.X_OK):
|
||||
msg = _("Cannot execute /sbin/mount.sofs")
|
||||
LOG.error(msg)
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
|
||||
def _load_shares_config(self, share_file=None):
|
||||
self.shares[self.sofs_rel_volume_dir] = None
|
||||
|
||||
def _get_mount_point_for_share(self, share=None):
|
||||
# The _qemu_img_info_base() method from the RemoteFSSnapDriver class
|
||||
# expects files (volume) to be inside a subdir of the mount point.
|
||||
# So we have to append a dummy subdir.
|
||||
return self.sofs_abs_volume_dir + "/00"
|
||||
|
||||
def _sofs_is_mounted(self):
|
||||
"""Check if SOFS is already mounted at the expected location."""
|
||||
mount_path = self.sofs_mount_point.rstrip('/')
|
||||
for mount in volume_utils.read_proc_mounts():
|
||||
parts = mount.split()
|
||||
if (parts[0].endswith('fuse') and
|
||||
parts[1].rstrip('/') == mount_path):
|
||||
return True
|
||||
return False
|
||||
|
||||
@lockutils.synchronized('mount-sofs', 'cinder-sofs', external=True)
|
||||
def _ensure_share_mounted(self, share=None):
|
||||
"""Mount SOFS if need be."""
|
||||
fileutils.ensure_tree(self.sofs_mount_point)
|
||||
|
||||
if not self._sofs_is_mounted():
|
||||
self._execute('mount', '-t', 'sofs', self.sofs_config,
|
||||
self.sofs_mount_point, run_as_root=True)
|
||||
# Check whether the mount command succeeded
|
||||
if not self._sofs_is_mounted():
|
||||
msg = _("Cannot mount Scality SOFS, check syslog for errors")
|
||||
LOG.error(msg)
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
|
||||
fileutils.ensure_tree(self.sofs_abs_volume_dir)
|
||||
|
||||
# We symlink the '00' subdir to its parent dir to maintain
|
||||
# compatibility with previous version of this driver.
|
||||
try:
|
||||
os.symlink(".", self._get_mount_point_for_share())
|
||||
except OSError as exc:
|
||||
if exc.errno == errno.EEXIST:
|
||||
if not os.path.islink(self._get_mount_point_for_share()):
|
||||
raise
|
||||
else:
|
||||
raise
|
||||
|
||||
def _ensure_shares_mounted(self):
|
||||
self._ensure_share_mounted()
|
||||
self._mounted_shares = [self.sofs_rel_volume_dir]
|
||||
|
||||
def _find_share(self, volume_size_for):
|
||||
try:
|
||||
return self._mounted_shares[0]
|
||||
except IndexError:
|
||||
raise exception.RemoteFSNoSharesMounted()
|
||||
|
||||
def get_volume_stats(self, refresh=False):
|
||||
"""Return the current state of the volume service."""
|
||||
stats = {
|
||||
'vendor_name': 'Scality',
|
||||
'driver_version': self.VERSION,
|
||||
'storage_protocol': 'scality',
|
||||
'total_capacity_gb': 'infinite',
|
||||
'free_capacity_gb': 'infinite',
|
||||
'reserved_percentage': 0,
|
||||
}
|
||||
backend_name = self.configuration.safe_get('volume_backend_name')
|
||||
stats['volume_backend_name'] = backend_name or self.volume_backend_name
|
||||
return stats
|
||||
|
||||
@remotefs_drv.locked_volume_id_operation
|
||||
def initialize_connection(self, volume, connector):
|
||||
"""Allow connection to connector and return connection info."""
|
||||
|
||||
# Find active qcow2 file
|
||||
active_file = self.get_active_image_from_info(volume)
|
||||
path = '%s/%s' % (self._get_mount_point_for_share(), active_file)
|
||||
sofs_rel_path = os.path.join(self.sofs_rel_volume_dir, "00",
|
||||
volume.name)
|
||||
|
||||
data = {'export': volume.provider_location,
|
||||
'name': active_file,
|
||||
'sofs_path': sofs_rel_path}
|
||||
|
||||
# Test file for raw vs. qcow2 format
|
||||
info = self._qemu_img_info(path, volume.name)
|
||||
data['format'] = info.file_format
|
||||
if data['format'] not in ['raw', 'qcow2']:
|
||||
msg = _('%s must be a valid raw or qcow2 image.') % path
|
||||
raise exception.InvalidVolume(msg)
|
||||
|
||||
return {
|
||||
'driver_volume_type': self.driver_volume_type,
|
||||
'data': data,
|
||||
'mount_point_base': self.sofs_mount_point
|
||||
}
|
||||
|
||||
def _qemu_img_info(self, path, volume_name):
|
||||
return super(ScalityDriver, self)._qemu_img_info_base(
|
||||
path, volume_name, self.sofs_abs_volume_dir)
|
||||
|
||||
@remotefs_drv.locked_volume_id_operation
|
||||
def extend_volume(self, volume, size_gb):
|
||||
volume_path = self.local_path(volume)
|
||||
|
||||
info = self._qemu_img_info(volume_path, volume.name)
|
||||
backing_fmt = info.file_format
|
||||
|
||||
if backing_fmt not in ['raw', 'qcow2']:
|
||||
msg = _('Unrecognized backing format: %s')
|
||||
raise exception.InvalidVolume(msg % backing_fmt)
|
||||
|
||||
# qemu-img can resize both raw and qcow2 files
|
||||
image_utils.resize_image(volume_path, size_gb)
|
||||
|
||||
def _copy_volume_from_snapshot(self, snapshot, volume, volume_size):
|
||||
"""Copy data from snapshot to destination volume.
|
||||
|
||||
This is done with a qemu-img convert to raw/qcow2 from the snapshot
|
||||
qcow2.
|
||||
"""
|
||||
|
||||
info_path = self._local_path_volume_info(snapshot.volume)
|
||||
|
||||
# For BC compat' with version < 2 of this driver
|
||||
try:
|
||||
snap_info = self._read_info_file(info_path)
|
||||
except IOError as exc:
|
||||
if exc.errno != errno.ENOENT:
|
||||
raise
|
||||
else:
|
||||
path_to_snap_img = self.local_path(snapshot)
|
||||
else:
|
||||
vol_path = self._local_volume_dir(snapshot.volume)
|
||||
|
||||
forward_file = snap_info[snapshot.id]
|
||||
forward_path = os.path.join(vol_path, forward_file)
|
||||
|
||||
# Find the file which backs this file, which represents the point
|
||||
# when this snapshot was created.
|
||||
img_info = self._qemu_img_info(forward_path,
|
||||
snapshot.volume.name)
|
||||
|
||||
path_to_snap_img = os.path.join(vol_path, img_info.backing_file)
|
||||
|
||||
LOG.debug("will copy from snapshot at %s", path_to_snap_img)
|
||||
|
||||
path_to_new_vol = self.local_path(volume)
|
||||
out_format = 'raw'
|
||||
image_utils.convert_image(path_to_snap_img,
|
||||
path_to_new_vol,
|
||||
out_format,
|
||||
run_as_root=self._execute_as_root)
|
||||
|
||||
self._set_rw_permissions_for_all(path_to_new_vol)
|
||||
|
||||
image_utils.resize_image(path_to_new_vol, volume_size)
|
||||
|
||||
def backup_volume(self, context, backup, backup_service):
|
||||
"""Create a new backup from an existing volume."""
|
||||
volume = self.db.volume_get(context, backup['volume_id'])
|
||||
volume_local_path = self.local_path(volume)
|
||||
LOG.info(_LI('Begin backup of volume %s.'), volume.name)
|
||||
|
||||
qemu_img_info = image_utils.qemu_img_info(volume_local_path)
|
||||
if qemu_img_info.file_format != 'raw':
|
||||
msg = _('Backup is only supported for raw-formatted '
|
||||
'SOFS volumes.')
|
||||
raise exception.InvalidVolume(msg)
|
||||
|
||||
if qemu_img_info.backing_file is not None:
|
||||
msg = _('Backup is only supported for SOFS volumes '
|
||||
'without backing file.')
|
||||
raise exception.InvalidVolume(msg)
|
||||
|
||||
with utils.temporary_chown(volume_local_path):
|
||||
with open(volume_local_path) as volume_file:
|
||||
backup_service.backup(backup, volume_file)
|
||||
|
||||
def restore_backup(self, context, backup, volume, backup_service):
|
||||
"""Restore an existing backup to a new or existing volume."""
|
||||
LOG.info(_LI('Restoring backup %(backup)s to volume %(volume)s.'),
|
||||
{'backup': backup['id'], 'volume': volume.name})
|
||||
volume_local_path = self.local_path(volume)
|
||||
with utils.temporary_chown(volume_local_path):
|
||||
with open(volume_local_path, 'wb') as volume_file:
|
||||
backup_service.restore(backup, volume.id, volume_file)
|
5
releasenotes/notes/remove-scality-fa209aae9748a1f3.yaml
Normal file
5
releasenotes/notes/remove-scality-fa209aae9748a1f3.yaml
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
upgrade:
|
||||
- The Scality backend volume driver was marked as not
|
||||
supported in the previous release and has now been
|
||||
removed.
|
Loading…
Reference in New Issue
Block a user