Add Cinder NFS driver for Dell PowerStore

Implements: blueprint powerstore-nfs-driver
Co-Authored-By: Alexander Malashenko <alexander.malashenko@dell.com>
Change-Id: Ide1d002acb8e1730767b15afc0566b2bb25999ed
This commit is contained in:
Ivan Pchelintsev 2021-06-16 13:20:10 +03:00 committed by Alexander Malashenko
parent db95ad2772
commit a5ce771687
7 changed files with 784 additions and 0 deletions

View File

@ -80,6 +80,8 @@ from cinder.volume.drivers.dell_emc.powermax import common as \
cinder_volume_drivers_dell_emc_powermax_common
from cinder.volume.drivers.dell_emc.powerstore import driver as \
cinder_volume_drivers_dell_emc_powerstore_driver
from cinder.volume.drivers.dell_emc.powerstore import nfs as \
cinder_volume_drivers_dell_emc_powerstore_nfs
from cinder.volume.drivers.dell_emc.powervault import common as \
cinder_volume_drivers_dell_emc_powervault_common
from cinder.volume.drivers.dell_emc.sc import storagecenter_common as \
@ -324,6 +326,7 @@ def list_opts():
cinder_volume_drivers_dell_emc_powermax_common.powermax_opts,
cinder_volume_drivers_dell_emc_powerstore_driver.
POWERSTORE_OPTS,
cinder_volume_drivers_dell_emc_powerstore_nfs.nfs_opts,
cinder_volume_drivers_dell_emc_powervault_common.common_opts,
cinder_volume_drivers_dell_emc_powervault_common.iscsi_opts,
cinder_volume_drivers_dell_emc_sc_storagecentercommon.

View File

@ -0,0 +1,462 @@
# Copyright (c) 2021 Dell Inc. or its subsidiaries.
# 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.
import errno
import os
from unittest import mock
import ddt
from oslo_concurrency import processutils as putils
from oslo_utils import imageutils
from oslo_utils import units
from cinder import context
from cinder import exception
from cinder.image import image_utils
from cinder.tests.unit import fake_constants as fake
from cinder.tests.unit import fake_snapshot
from cinder.tests.unit import fake_volume
from cinder.tests.unit import test
from cinder.volume import configuration as conf
from cinder.volume.drivers.dell_emc.powerstore import nfs
from cinder.volume import volume_utils
NFS_CONFIG = {'max_over_subscription_ratio': 1.0,
'reserved_percentage': 0,
'nfs_sparsed_volumes': True,
'nfs_qcow2_volumes': False,
'nas_secure_file_permissions': 'false',
'nas_secure_file_operations': 'false'}
QEMU_IMG_INFO_OUT1 = """image: %(volid)s
file format: raw
virtual size: %(size_gb)sG (%(size_b)s bytes)
disk size: 173K
"""
QEMU_IMG_INFO_OUT2 = """image: %(volid)s
file format: qcow2
virtual size: %(size_gb)sG (%(size_b)s bytes)
disk size: 173K
"""
QEMU_IMG_INFO_OUT3 = """image: volume-%(volid)s.%(snapid)s
file format: qcow2
virtual size: %(size_gb)sG (%(size_b)s bytes)
disk size: 196K
cluster_size: 65536
backing file: volume-%(volid)s
backing file format: qcow2
Format specific information:
compat: 1.1
lazy refcounts: false
refcount bits: 16
corrupt: false
"""
@ddt.ddt
class PowerStoreNFSDriverInitializeTestCase(test.TestCase):
TEST_NFS_HOST = 'nfs-host1'
def setUp(self):
super(PowerStoreNFSDriverInitializeTestCase, self).setUp()
self.context = mock.Mock()
self.create_configuration()
self.override_config('compute_api_class', 'unittest.mock.Mock')
self.drv = nfs.PowerStoreNFSDriverInitialization(
configuration=self.configuration)
def create_configuration(self):
config = conf.Configuration(None)
config.append_config_values(nfs.nfs_opts)
self.configuration = config
def test_check_multiattach_support(self):
drv = self.drv
self.configuration.nfs_qcow2_volumes = False
drv._check_multiattach_support()
self.assertEqual(not self.configuration.nfs_qcow2_volumes,
drv.multiattach_support)
def test_check_multiattach_support_disable(self):
drv = self.drv
drv.configuration.nfs_qcow2_volumes = True
drv._check_multiattach_support()
self.assertEqual(not self.configuration.nfs_qcow2_volumes,
drv.multiattach_support)
def test_check_snapshot_support(self):
drv = self.drv
drv.configuration.nfs_snapshot_support = True
drv.configuration.nas_secure_file_operations = 'false'
drv._check_snapshot_support()
self.assertTrue(drv.configuration.nfs_snapshot_support)
def test_check_snapshot_support_disable(self):
drv = self.drv
drv.configuration.nfs_snapshot_support = False
drv.configuration.nas_secure_file_operations = 'false'
self.assertRaises(exception.VolumeDriverException,
drv._check_snapshot_support)
def test_check_snapshot_support_nas_true(self):
drv = self.drv
drv.configuration.nfs_snapshot_support = True
drv.configuration.nas_secure_file_operations = 'true'
self.assertRaises(exception.VolumeDriverException,
drv._check_snapshot_support)
@mock.patch("cinder.volume.drivers.nfs.NfsDriver.do_setup")
def test_do_setup(self, mock_super_do_setup):
drv = self.drv
drv.configuration.nas_host = self.TEST_NFS_HOST
mock_check_multiattach_support = self.mock_object(
drv, '_check_multiattach_support'
)
drv.do_setup(self.context)
self.assertTrue(mock_check_multiattach_support.called)
def test_check_package_is_installed(self):
drv = self.drv
package = 'dellfcopy'
mock_execute = self.mock_object(drv, '_execute')
drv._check_package_is_installed(package)
mock_execute.assert_called_once_with(package,
check_exit_code=False,
run_as_root=False)
def test_check_package_is_not_installed(self):
drv = self.drv
package = 'dellfcopy'
drv._execute = mock.Mock(
side_effect=OSError(
errno.ENOENT, 'No such file or directory'
)
)
self.assertRaises(exception.VolumeDriverException,
drv._check_package_is_installed, package)
drv._execute.assert_called_once_with(package,
check_exit_code=False,
run_as_root=False)
def test_check_for_setup_error(self):
drv = self.drv
mock_check_package_is_installed = self.mock_object(
drv, '_check_package_is_installed')
drv.check_for_setup_error()
mock_check_package_is_installed.assert_called_once_with('dellfcopy')
def test_check_for_setup_error_not_passed(self):
drv = self.drv
drv._execute = mock.Mock(
side_effect=OSError(
errno.ENOENT, 'No such file or directory'
)
)
self.assertRaises(exception.VolumeDriverException,
drv.check_for_setup_error)
drv._execute.assert_called_once_with('dellfcopy',
check_exit_code=False,
run_as_root=False)
def test_update_volume_stats_has_multiattach(self):
drv = self.drv
self.mock_object(nfs.NfsDriver, '_update_volume_stats')
drv.multiattach_support = True
drv._stats = {}
drv._update_volume_stats()
self.assertIn('multiattach', drv._stats)
self.assertTrue(drv._stats['multiattach'])
@ddt.ddt
class PowerStoreNFSDriverTestCase(test.TestCase):
TEST_NFS_HOST = 'nfs-host1'
TEST_NFS_SHARE_PATH = '/export'
TEST_NFS_EXPORT = '%s:%s' % (TEST_NFS_HOST, TEST_NFS_SHARE_PATH)
TEST_SIZE_IN_GB = 1
TEST_MNT_POINT = '/mnt/nfs'
TEST_MNT_POINT_BASE_EXTRA_SLASH = '/opt/stack/data/cinder//mnt'
TEST_MNT_POINT_BASE = '/mnt/test'
TEST_LOCAL_PATH = '/mnt/nfs/volume-123'
TEST_FILE_NAME = 'test.txt'
VOLUME_UUID = 'abcdefab-cdef-abcd-efab-cdefabcdefab'
def setUp(self):
super(PowerStoreNFSDriverTestCase, self).setUp()
self.configuration = mock.Mock(conf.Configuration)
self.configuration.append_config_values(mock.ANY)
self.configuration.nfs_sparsed_volumes = True
self.configuration.nas_secure_file_permissions = 'false'
self.configuration.nas_secure_file_operations = 'false'
self.configuration.nfs_mount_point_base = self.TEST_MNT_POINT_BASE
self.configuration.nfs_snapshot_support = True
self.configuration.max_over_subscription_ratio = 1.0
self.configuration.reserved_percentage = 5
self.configuration.nfs_mount_options = None
self.configuration.nfs_qcow2_volumes = True
self.configuration.nas_host = '0.0.0.0'
self.configuration.nas_share_path = None
self.mock_object(volume_utils, 'get_max_over_subscription_ratio',
return_value=1)
self.context = context.get_admin_context()
self._driver = nfs.PowerStoreNFSDriver(
configuration=self.configuration)
self._driver.shares = {}
self.mock_object(self._driver, '_execute')
def test_do_fast_clone_file(self):
drv = self._driver
volume_path = 'fake/path'
new_volume_path = 'fake/new_path'
drv._do_fast_clone_file(volume_path, new_volume_path)
drv._execute.assert_called_once_with(
'dellfcopy', '-o', 'fastclone', '-s', volume_path, '-d',
new_volume_path, '-v', '1', run_as_root=True
)
def test_do_fast_clone_file_raise_error(self):
drv = self._driver
volume_path = 'fake/path'
new_volume_path = 'fake/new_path'
drv._execute = mock.Mock(
side_effect=putils.ProcessExecutionError()
)
self.assertRaises(putils.ProcessExecutionError,
drv._do_fast_clone_file, volume_path,
new_volume_path)
drv._execute.assert_called_once_with(
'dellfcopy', '-o', 'fastclone', '-s', volume_path, '-d',
new_volume_path, '-v', '1', run_as_root=True
)
def _simple_volume(self, **kwargs):
updates = {'id': self.VOLUME_UUID,
'provider_location': self.TEST_NFS_EXPORT,
'display_name': f'volume-{self.VOLUME_UUID}',
'name': f'volume-{self.VOLUME_UUID}',
'size': 10,
'status': 'available'}
updates.update(kwargs)
if 'display_name' not in updates:
updates['display_name'] = 'volume-%s' % updates['id']
return fake_volume.fake_volume_obj(self.context, **updates)
def test_delete_volume_without_info(self):
drv = self._driver
volume = fake_volume.fake_volume_obj(
self.context,
display_name='volume',
provider_location=self.TEST_NFS_EXPORT
)
vol_path = '/path/to/vol'
mock_ensure_share_mounted = self.mock_object(
drv, '_ensure_share_mounted')
mock_local_path_volume_info = self.mock_object(
drv, '_local_path_volume_info'
)
mock_local_path_volume_info.return_value = self.TEST_LOCAL_PATH
mock_read_info_file = self.mock_object(drv, '_read_info_file')
mock_read_info_file.return_value = {}
mock_local_path_volume = self.mock_object(drv, '_local_path_volume')
mock_local_path_volume.return_value = vol_path
drv.delete_volume(volume)
mock_ensure_share_mounted.assert_called_once_with(
self.TEST_NFS_EXPORT)
mock_local_path_volume.assert_called_once_with(volume)
mock_read_info_file.assert_called_once_with(
self.TEST_LOCAL_PATH, empty_if_missing=True)
mock_local_path_volume.assert_called_once_with(volume)
drv._execute.assert_called_once_with(
'rm', '-f', vol_path, run_as_root=True)
def test_delete_volume_with_info(self):
drv = self._driver
volume = fake_volume.fake_volume_obj(
self.context,
display_name='volume',
provider_location=self.TEST_NFS_EXPORT
)
vol_path = '/path/to/vol'
with mock.patch.object(drv, '_ensure_share_mounted'):
mock_local_path_volume_info = self.mock_object(
drv, '_local_path_volume_info'
)
mock_local_path_volume_info.return_value = self.TEST_LOCAL_PATH
mock_read_info_file = self.mock_object(drv, '_read_info_file')
mock_read_info_file.return_value = {'active': '/path/to/active'}
mock_local_path_volume = self.mock_object(
drv, '_local_path_volume')
mock_local_path_volume.return_value = vol_path
drv.delete_volume(volume)
self.assertEqual(drv._execute.call_count, 3)
def test_delete_volume_without_provider_location(self):
drv = self._driver
volume = fake_volume.fake_volume_obj(
self.context,
display_name='volume',
provider_location=''
)
drv.delete_volume(volume)
self.assertFalse(bool(drv._execute.call_count))
@ddt.data([None, QEMU_IMG_INFO_OUT1],
['raw', QEMU_IMG_INFO_OUT1],
['qcow2', QEMU_IMG_INFO_OUT2])
@ddt.unpack
@mock.patch('cinder.objects.volume.Volume.get_by_id')
def test_extend_volume(self, file_format, qemu_img_info, mock_get):
drv = self._driver
volume = fake_volume.fake_volume_obj(
self.context,
id='80ee16b6-75d2-4d54-9539-ffc1b4b0fb10',
size=1,
provider_location='nfs_share')
if file_format:
volume.admin_metadata = {'format': file_format}
mock_get.return_value = volume
path = 'path'
new_size = volume['size'] + 1
mock_img_utils = self.mock_object(drv, '_qemu_img_info')
img_out = qemu_img_info % {'volid': volume.id,
'size_gb': volume.size,
'size_b': volume.size * units.Gi}
mock_img_utils.return_value = imageutils.QemuImgInfo(
img_out)
with mock.patch.object(image_utils, 'resize_image') as resize:
with mock.patch.object(drv, 'local_path', return_value=path):
with mock.patch.object(drv, '_is_share_eligible',
return_value=True):
drv.extend_volume(volume, new_size)
resize.assert_called_once_with(path, new_size)
def test_create_volume_from_snapshot(self):
drv = self._driver
src_volume = self._simple_volume(size=10)
src_volume.id = fake.VOLUME_ID
fake_snap = fake_snapshot.fake_snapshot_obj(self.context)
fake_snap.volume = src_volume
fake_snap.size = 10
fake_snap.status = 'available'
new_volume = self._simple_volume(size=src_volume.size)
drv._find_share = mock.Mock(return_value=self.TEST_NFS_EXPORT)
drv._copy_volume_from_snapshot = mock.Mock()
drv._create_volume_from_snapshot(new_volume, fake_snap)
drv._find_share.assert_called_once_with(new_volume)
drv._copy_volume_from_snapshot.assert_called_once_with(
fake_snap, new_volume, new_volume.size
)
@mock.patch('cinder.objects.volume.Volume.get_by_id')
def test_create_cloned_volume(self, mock_get):
drv = self._driver
volume = self._simple_volume()
mock_get.return_value = volume
vol_dir = os.path.join(self.TEST_MNT_POINT_BASE,
drv._get_hash_str(volume.provider_location))
vol_path = os.path.join(vol_dir, volume.name)
new_volume = self._simple_volume()
new_vol_dir = os.path.join(self.TEST_MNT_POINT_BASE,
drv._get_hash_str(
volume.provider_location))
new_vol_path = os.path.join(new_vol_dir, volume.name)
drv._create_cloned_volume(new_volume, volume, self.context)
command = ['dellfcopy', '-o', 'fastclone', '-s', vol_path,
'-d', new_vol_path, '-v', '1']
calls = [mock.call(*command, run_as_root=True)]
drv._execute.assert_has_calls(calls)
@ddt.data([QEMU_IMG_INFO_OUT3])
@ddt.unpack
@mock.patch('cinder.objects.volume.Volume.save')
def test_copy_volume_from_snapshot(self, qemu_img_info, mock_save):
drv = self._driver
src_volume = self._simple_volume(size=10)
src_volume.id = fake.VOLUME_ID
fake_snap = fake_snapshot.fake_snapshot_obj(self.context)
snap_file = src_volume.name + '.' + fake_snap.id
fake_snap.volume = src_volume
fake_snap.size = 10
fake_source_vol_path = os.path.join(
drv._local_volume_dir(fake_snap.volume),
src_volume.name
)
new_volume = self._simple_volume(size=10)
new_vol_dir = os.path.join(self.TEST_MNT_POINT_BASE,
drv._get_hash_str(
src_volume.provider_location))
new_vol_path = os.path.join(new_vol_dir, new_volume.name)
mock_read_info_file = self.mock_object(drv, '_read_info_file')
mock_read_info_file.return_value = {'active': snap_file,
fake_snap.id: snap_file}
mock_img_utils = self.mock_object(drv, '_qemu_img_info')
img_out = qemu_img_info % {'volid': src_volume.id,
'snapid': fake_snap.id,
'size_gb': src_volume.size,
'size_b': src_volume.size * units.Gi}
mock_img_utils.return_value = imageutils.QemuImgInfo(img_out)
drv._copy_volume_from_snapshot(fake_snap, new_volume, new_volume.size)
command = ['dellfcopy', '-o', 'fastclone', '-s', fake_source_vol_path,
'-d', new_vol_path, '-v', '1']
calls = [mock.call(*command, run_as_root=True)]
drv._execute.assert_has_calls(calls)

View File

@ -0,0 +1,238 @@
# Copyright (c) 2020 Dell Inc. or its subsidiaries.
# 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.
import collections
import errno
import os
from oslo_concurrency import processutils as putils
from oslo_config import cfg
from oslo_log import log as logging
from cinder import coordination
from cinder import exception
from cinder.i18n import _
from cinder.image import image_utils
from cinder import interface
from cinder.volume import configuration
from cinder.volume.drivers.nfs import nfs_opts
from cinder.volume.drivers.nfs import NfsDriver
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
CONF.register_opts(nfs_opts, group=configuration.SHARED_CONF_GROUP)
class PowerStoreNFSDriverInitialization(NfsDriver):
"""Implementation of PowerStoreNFSDriver initialization.
Added multiattach support option and checking that the required
packages are installed.
"""
driver_volume_type = 'nfs'
driver_prefix = 'nfs'
volume_backend_name = 'PowerStore_NFS'
VERSION = '1.0.0'
VENDOR = 'Dell'
# ThirdPartySystems wiki page
CI_WIKI_NAME = "DellEMC_PowerStore_CI"
def __init__(self, execute=putils.execute, *args, **kwargs):
super(PowerStoreNFSDriverInitialization, self).__init__(
execute, *args, **kwargs)
self.multiattach_support = False
def do_setup(self, context):
super(PowerStoreNFSDriverInitialization, self).do_setup(context)
self._check_multiattach_support()
def _check_multiattach_support(self):
"""Enable multiattach support if nfs_qcow2_volumes disabled."""
self.multiattach_support = not self.configuration.nfs_qcow2_volumes
if not self.multiattach_support:
msg = _("Multi-attach feature won't work "
"with qcow2 volumes enabled for nfs")
LOG.warning(msg)
def _check_package_is_installed(self, package):
try:
self._execute(package, check_exit_code=False,
run_as_root=False)
except OSError as exc:
if exc.errno == errno.ENOENT:
msg = _('%s is not installed') % package
raise exception.VolumeDriverException(msg)
else:
raise
def check_for_setup_error(self):
self._check_package_is_installed('dellfcopy')
def _update_volume_stats(self):
super(PowerStoreNFSDriverInitialization, self)._update_volume_stats()
self._stats["vendor_name"] = self.VENDOR
self._stats['multiattach'] = self.multiattach_support
@interface.volumedriver
class PowerStoreNFSDriver(PowerStoreNFSDriverInitialization):
"""Dell PowerStore NFS Driver.
.. code-block:: none
Version history:
1.0.0 - Initial version
"""
@coordination.synchronized('{self.driver_prefix}-{volume[id]}')
def delete_volume(self, volume):
"""Deletes a logical volume."""
if not volume.provider_location:
LOG.warning("Volume %s does not have provider_location "
"specified, skipping", volume.name)
return
self._ensure_share_mounted(volume.provider_location)
info_path = self._local_path_volume_info(volume)
info = self._read_info_file(info_path, empty_if_missing=True)
if info:
base_snap_path = os.path.join(self._local_volume_dir(volume),
info['active'])
self._delete(info_path)
self._delete(base_snap_path)
base_volume_path = self._local_path_volume(volume)
self._delete(base_volume_path)
def extend_volume(self, volume, new_size):
"""Extend an existing volume to the new size."""
if self._is_volume_attached(volume):
msg = (_("Cannot extend volume %s while it is attached")
% volume.name_id)
raise exception.ExtendVolumeError(msg)
LOG.info("Extending volume %s.", volume.name_id)
extend_by = int(new_size) - volume.size
if not self._is_share_eligible(volume.provider_location,
extend_by):
raise exception.ExtendVolumeError(
reason="Insufficient space to extend "
"volume %s to %sG" % (volume.name_id, new_size)
)
path = self.local_path(volume)
info = self._qemu_img_info(path, volume.name)
backing_fmt = info.file_format
if backing_fmt not in ['raw', 'qcow2']:
msg = _("Unrecognized backing format: %s") % backing_fmt
raise exception.InvalidVolume(msg)
image_utils.resize_image(path, new_size)
def _do_fast_clone_file(self, volume_path, new_volume_path):
"""Fast clone a file using a dellfcopy package."""
command = ['dellfcopy', '-o', 'fastclone', '-s', volume_path,
'-d', new_volume_path, '-v', '1']
try:
LOG.info('Cloning file from %s to %s',
volume_path, new_volume_path)
self._execute(*command, run_as_root=self._execute_as_root)
LOG.info('Cloning volume: %s succeeded', volume_path)
except putils.ProcessExecutionError:
raise
def _create_volume_from_snapshot(self, volume, snapshot):
"""Creates a volume from a snapshot."""
LOG.debug("Creating volume %(vol)s from snapshot %(snap)s",
{'vol': volume.name_id, 'snap': snapshot.id})
volume.provider_location = self._find_share(volume)
self._copy_volume_from_snapshot(snapshot, volume, volume.size)
return {'provider_location': volume.provider_location}
def _copy_volume_from_snapshot(self, snapshot, volume, volume_size,
src_encryption_key_id=None,
new_encryption_key_id=None):
"""Copy snapshot to destination volume."""
LOG.debug("snapshot: %(snap)s, volume: %(vol)s, ",
{'snap': snapshot.id,
'vol': volume.id,
'size': volume_size})
info_path = self._local_path_volume_info(snapshot.volume)
snap_info = self._read_info_file(info_path)
vol_path = self._local_volume_dir(snapshot.volume)
forward_file = snap_info[snapshot.id]
forward_path = os.path.join(vol_path, forward_file)
img_info = self._qemu_img_info(forward_path, snapshot.volume.name)
path_to_snap_img = os.path.join(vol_path, img_info.backing_file)
path_to_new_vol = self._local_path_volume(volume)
if img_info.backing_file_format == 'raw':
image_utils.convert_image(path_to_snap_img,
path_to_new_vol,
img_info.backing_file_format,
run_as_root=self._execute_as_root)
else:
self._do_fast_clone_file(path_to_snap_img, path_to_new_vol)
command = ['qemu-img', 'rebase', '-b', "", '-F',
img_info.backing_file_format, path_to_new_vol]
self._execute(*command, run_as_root=self._execute_as_root)
self._set_rw_permissions(path_to_new_vol)
def _create_cloned_volume(self, volume, src_vref, context):
"""Clone src volume to destination volume."""
LOG.debug('Cloning volume %(src)s to volume %(dst)s',
{'src': src_vref.id, 'dst': volume.id})
volume_name = CONF.volume_name_template % volume.name_id
vol_attrs = ['provider_location', 'size', 'id', 'name', 'status',
'volume_type', 'metadata', 'obj_context']
Volume = collections.namedtuple('Volume', vol_attrs)
volume_info = Volume(provider_location=src_vref.provider_location,
size=src_vref.size,
id=volume.name_id,
name=volume_name,
status=src_vref.status,
volume_type=src_vref.volume_type,
metadata=src_vref.metadata,
obj_context=volume.obj_context)
src_volue_path = self._local_path_volume(src_vref)
dst_volume = self._local_path_volume(volume_info)
self._do_fast_clone_file(src_volue_path, dst_volume)
if src_vref.admin_metadata and 'format' in src_vref.admin_metadata:
volume.admin_metadata['format'] = (
src_vref.admin_metadata['format'])
with volume.obj_as_admin():
volume.save()
return {'provider_location': src_vref.provider_location}

View File

@ -0,0 +1,61 @@
==========================
Dell PowerStore NFS Driver
==========================
PowerStore NFS driver enables storing Block Storage service volumes on a
PowerStore storage back end.
Supported operations
~~~~~~~~~~~~~~~~~~~~
- Create, delete, attach and detach volumes.
- Create, delete volume snapshots.
- Create a volume from a snapshot.
- Copy an image to a volume.
- Copy a volume to an image.
- Clone a volume.
- Extend a volume.
- Get volume statistics.
- Attach a volume to multiple servers simultaneously (multiattach).
- Revert a volume to a snapshot.
Driver configuration
~~~~~~~~~~~~~~~~~~~~
Add the following content into ``/etc/cinder/cinder.conf``:
.. code-block:: ini
[DEFAULT]
enabled_backends = powerstore-nfs
[powerstore-nfs]
volume_driver = cinder.volume.drivers.dell_emc.powerstore.nfs.PowerStoreNFSDriver
nfs_qcow2_volumes = True
nfs_snapshot_support = True
nfs_sparsed_volumes = False
nas_host = <Ip>
nas_share_path = /nfs-export
nas_secure_file_operations = False
nas_secure_file_permissions = False
volume_backend_name = powerstore-nfs
Dell PowerStore NFS Copy Offload API
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A feature for effective creation of a volume from snapshot/volume was added
in PowerStore NFS Driver. The dellfcopy utility provides the ability to copy
a file very quickly on a Dell SDNAS filesystem mounted by a client.
To download it, contact your local Dell representative.
The dellfcopy tool is used in the following operations:
- Create a volume from a snapshot.
- Clone a volume.
To use PowerStore NFS driver with this feature, you must install the tool with
the following command:
.. code-block:: console
# sudo dpkg -i ./dellfcopy_1.3-1_amd64.deb

View File

@ -27,6 +27,9 @@ title=Dell PowerMax (2000, 8000) Storage Driver (iSCSI, FC)
[driver.dell_emc_powerstore]
title=Dell PowerStore Storage Driver (iSCSI, FC)
[driver.dell_emc_powerstore_nfs]
title=Dell PowerStore NFS Driver (NFS)
[driver.dell_emc_sc]
title=Dell SC Series Storage Driver (iSCSI, FC)
@ -226,6 +229,7 @@ notes=A vendor driver is considered supported if the vendor is
driver.datera=complete
driver.dell_emc_powermax=complete
driver.dell_emc_powerstore=complete
driver.dell_emc_powerstore_nfs=complete
driver.dell_emc_powervault=complete
driver.dell_emc_sc=complete
driver.dell_emc_unity=complete
@ -298,6 +302,7 @@ notes=Cinder supports the ability to extend a volume that is attached to
driver.datera=complete
driver.dell_emc_powermax=complete
driver.dell_emc_powerstore=complete
driver.dell_emc_powerstore_nfs=missing
driver.dell_emc_powervault=complete
driver.dell_emc_sc=complete
driver.dell_emc_unity=complete
@ -373,6 +378,7 @@ notes=Vendor drivers that support Quality of Service (QoS) at the
driver.datera=complete
driver.dell_emc_powermax=complete
driver.dell_emc_powerstore=missing
driver.dell_emc_powerstore_nfs=missing
driver.dell_emc_powervault=missing
driver.dell_emc_sc=complete
driver.dell_emc_unity=complete
@ -447,6 +453,7 @@ notes=Vendor drivers that support volume replication can report this
driver.datera=missing
driver.dell_emc_powermax=complete
driver.dell_emc_powerstore=complete
driver.dell_emc_powerstore_nfs=missing
driver.dell_emc_powervault=missing
driver.dell_emc_sc=complete
driver.dell_emc_unity=complete
@ -522,6 +529,7 @@ notes=Vendor drivers that support consistency groups are able to
driver.datera=missing
driver.dell_emc_powermax=complete
driver.dell_emc_powerstore=complete
driver.dell_emc_powerstore_nfs=missing
driver.dell_emc_powervault=missing
driver.dell_emc_sc=complete
driver.dell_emc_unity=complete
@ -596,6 +604,7 @@ notes=If a volume driver supports thin provisioning it means that it
driver.datera=missing
driver.dell_emc_powermax=complete
driver.dell_emc_powerstore=complete
driver.dell_emc_powerstore_nfs=complete
driver.dell_emc_powervault=missing
driver.dell_emc_sc=complete
driver.dell_emc_unity=complete
@ -671,6 +680,7 @@ notes=Storage assisted volume migration is like host assisted volume
driver.datera=missing
driver.dell_emc_powermax=complete
driver.dell_emc_powerstore=missing
driver.dell_emc_powerstore_nfs=missing
driver.dell_emc_powervault=missing
driver.dell_emc_sc=missing
driver.dell_emc_unity=complete
@ -746,6 +756,7 @@ notes=Vendor drivers that report multi-attach support are able
driver.datera=missing
driver.dell_emc_powermax=complete
driver.dell_emc_powerstore=complete
driver.dell_emc_powerstore_nfs=complete
driver.dell_emc_powervault=complete
driver.dell_emc_sc=complete
driver.dell_emc_unity=complete
@ -818,6 +829,7 @@ notes=Vendor drivers that implement the driver assisted function to revert a
driver.datera=missing
driver.dell_emc_powermax=complete
driver.dell_emc_powerstore=complete
driver.dell_emc_powerstore_nfs=missing
driver.dell_emc_powervault=missing
driver.dell_emc_sc=missing
driver.dell_emc_unity=complete
@ -894,6 +906,7 @@ notes=Vendor drivers that support running in an active/active
driver.datera=missing
driver.dell_emc_powermax=missing
driver.dell_emc_powerstore=missing
driver.dell_emc_powerstore_nfs=missing
driver.dell_emc_powervault=missing
driver.dell_emc_sc=missing
driver.dell_emc_unity=missing

View File

@ -137,4 +137,7 @@ ploop: CommandFilter, ploop, root
mount.quobyte: CommandFilter, mount.quobyte, root
umount.quobyte: CommandFilter, umount.quobyte, root
# cinder/volume/drivers/dell_emc/powerstore/nfs.py
dellfcopy: CommandFilter, dellfcopy, root
cryptsetup: CommandFilter, cryptsetup, root

View File

@ -0,0 +1,4 @@
---
features:
- |
Dell PowerStore: Added NFS storage driver.