Implementation of Cinder driver for Veritas Access
This driver implements all the minimum required features for cinder. DocImpact Change-Id: I5650ab23a5aafbbc8aed5953e85235d9c97ce760 Implement: blueprint veritas-access-cinder-driver
This commit is contained in:
parent
b441c6d151
commit
5993af92ef
177
cinder/tests/unit/volume/drivers/test_veritas_cnfs.py
Normal file
177
cinder/tests/unit/volume/drivers/test_veritas_cnfs.py
Normal file
@ -0,0 +1,177 @@
|
||||
# Copyright (c) 2017 Veritas Technologies LLC
|
||||
# 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 os
|
||||
|
||||
import mock
|
||||
|
||||
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
|
||||
from cinder.volume.drivers import veritas_cnfs as cnfs
|
||||
|
||||
|
||||
class VeritasCNFSDriverTestCase(test.TestCase):
|
||||
|
||||
"""Test case for VeritasCNFS driver."""
|
||||
TEST_CNFS_SHARE = 'cnfs-host1:/share'
|
||||
TEST_VOL_NM = 'volume-a6707cd3-348c-45cd-9524-255be0939b60'
|
||||
TEST_SNAP_NM = 'snapshot-73368c68-1c0b-4027-ba8a-14629918945e'
|
||||
TEST_VOL_SIZE = 1
|
||||
TEST_MNT_BASE = '/cnfs/share'
|
||||
TEST_LOCAL_PATH = '/cnfs/share/mnt'
|
||||
TEST_VOL_LOCAL_PATH = TEST_LOCAL_PATH + '/' + TEST_VOL_NM
|
||||
TEST_SNAP_LOCAL_PATH = TEST_LOCAL_PATH + '/' + TEST_SNAP_NM
|
||||
TEST_SPL_SNAP_LOCAL_PATH = TEST_SNAP_LOCAL_PATH + "::snap:vxfs:"
|
||||
TEST_NFS_SHARES_CONFIG = '/etc/cinder/access_nfs_share'
|
||||
TEST_NFS_MOUNT_OPTIONS_FAIL_NONE = ''
|
||||
TEST_NFS_MOUNT_OPTIONS_FAIL_V4 = 'nfsvers=4'
|
||||
TEST_NFS_MOUNT_OPTIONS_FAIL_V2 = 'nfsvers=2'
|
||||
TEST_NFS_MOUNT_OPTIONS_PASS_V3 = 'nfsvers=3'
|
||||
TEST_VOL_ID = 'a6707cd3-348c-45cd-9524-255be0939b60'
|
||||
SNAPSHOT_ID = '73368c68-1c0b-4027-ba8a-14629918945e'
|
||||
|
||||
def setUp(self):
|
||||
super(VeritasCNFSDriverTestCase, self).setUp()
|
||||
self.configuration = mock.Mock(conf.Configuration)
|
||||
self.configuration.nfs_shares_config = self.TEST_NFS_SHARES_CONFIG
|
||||
self.configuration.nfs_sparsed_volumes = True
|
||||
self.configuration.nfs_mount_point_base = self.TEST_MNT_BASE
|
||||
self.configuration.nfs_mount_options = (self.
|
||||
TEST_NFS_MOUNT_OPTIONS_PASS_V3)
|
||||
self.configuration.nfs_oversub_ratio = 1.0
|
||||
self.configuration.nfs_used_ratio = 0.95
|
||||
self.configuration.nfs_disk_util = 'df'
|
||||
self.configuration.reserved_percentage = 0
|
||||
self.configuration.max_over_subscription_ratio = 20.0
|
||||
self.configuration.nas_secure_file_permissions = 'false'
|
||||
self.configuration.nas_secure_file_operations = 'false'
|
||||
self._loc = 'localhost:/share'
|
||||
self.context = context.get_admin_context()
|
||||
self.driver = cnfs.VeritasCNFSDriver(configuration=self.configuration)
|
||||
|
||||
def test_throw_error_if_nfs_mount_options_not_configured(self):
|
||||
"""Fail if no nfs mount options are configured"""
|
||||
drv = self.driver
|
||||
none_opts = self.TEST_NFS_MOUNT_OPTIONS_FAIL_NONE
|
||||
self.configuration.nfs_mount_options = none_opts
|
||||
self.assertRaises(
|
||||
exception.NfsException, drv.do_setup, context.RequestContext)
|
||||
|
||||
def test_throw_error_if_nfs_mount_options_configured_with_NFSV2(self):
|
||||
"""Fail if nfs mount options is not nfsv4 """
|
||||
drv = self.driver
|
||||
nfs_v2_opts = self.TEST_NFS_MOUNT_OPTIONS_FAIL_V2
|
||||
self.configuration.nfs_mount_options = nfs_v2_opts
|
||||
self.assertRaises(
|
||||
exception.NfsException, drv.do_setup, context.RequestContext)
|
||||
|
||||
def test_throw_error_if_nfs_mount_options_configured_with_NFSV4(self):
|
||||
"""Fail if nfs mount options is not nfsv4 """
|
||||
drv = self.driver
|
||||
nfs_v4_opts = self.TEST_NFS_MOUNT_OPTIONS_FAIL_V4
|
||||
self.configuration.nfs_mount_options = nfs_v4_opts
|
||||
self.assertRaises(
|
||||
exception.NfsException, drv.do_setup, context.RequestContext)
|
||||
|
||||
@mock.patch.object(cnfs.VeritasCNFSDriver, '_get_local_volume_path')
|
||||
@mock.patch.object(os.path, 'exists')
|
||||
def test_do_clone_volume_success(self, m_exists, m_get_local_volume_path):
|
||||
"""test _do_clone_volume() when filesnap over nfs is supported"""
|
||||
drv = self.driver
|
||||
volume = fake_volume.fake_volume_obj(self.context,
|
||||
provider_location=self._loc)
|
||||
snapshot = fake_volume.fake_volume_obj(self.context)
|
||||
with mock.patch.object(drv, '_execute'):
|
||||
m_exists.return_value = True
|
||||
drv._do_clone_volume(volume, volume.name, snapshot)
|
||||
|
||||
@mock.patch.object(cnfs.VeritasCNFSDriver, '_get_local_volume_path')
|
||||
@mock.patch.object(os.path, 'exists')
|
||||
def test_do_clone_volume_fail(self, m_exists, m_get_local_volume_path):
|
||||
"""test _do_clone_volume() when filesnap over nfs is supported"""
|
||||
drv = self.driver
|
||||
volume = fake_volume.fake_volume_obj(self.context)
|
||||
snapshot = fake_volume.fake_volume_obj(self.context)
|
||||
with mock.patch.object(drv, '_execute'):
|
||||
m_exists.return_value = False
|
||||
self.assertRaises(exception.NfsException, drv._do_clone_volume,
|
||||
volume, volume.name, snapshot)
|
||||
|
||||
def assign_provider_loc(self, src_vol, tgt_vol):
|
||||
tgt_vol.provider_location = src_vol.provider_location
|
||||
|
||||
@mock.patch.object(cnfs.VeritasCNFSDriver, '_do_clone_volume')
|
||||
def test_create_volume_from_snapshot(self, m_do_clone_volume):
|
||||
"""test create volume from snapshot"""
|
||||
drv = self.driver
|
||||
volume = fake_volume.fake_volume_obj(self.context)
|
||||
snapshot = fake_volume.fake_volume_obj(self.context,
|
||||
provider_location=self._loc)
|
||||
volume.size = 10
|
||||
snapshot.volume_size = 10
|
||||
m_do_clone_volume(snapshot, snapshot.name,
|
||||
volume).return_value = True
|
||||
drv.create_volume_from_snapshot(volume, snapshot)
|
||||
self.assertEqual(volume.provider_location, snapshot.provider_location)
|
||||
|
||||
@mock.patch.object(cnfs.VeritasCNFSDriver, '_get_vol_by_id')
|
||||
@mock.patch.object(cnfs.VeritasCNFSDriver, '_do_clone_volume')
|
||||
def test_create_snapshot(self, m_do_clone_volume, m_get_vol_by_id):
|
||||
"""test create snapshot"""
|
||||
drv = self.driver
|
||||
volume = fake_volume.fake_volume_obj(context.get_admin_context(),
|
||||
provider_location=self._loc)
|
||||
snapshot = fake_snapshot.fake_snapshot_obj(context.get_admin_context())
|
||||
snapshot.volume = volume
|
||||
m_get_vol_by_id.return_value = volume
|
||||
m_do_clone_volume(snapshot, snapshot.name,
|
||||
volume).return_value = True
|
||||
drv.create_snapshot(snapshot)
|
||||
self.assertEqual(volume.provider_location, snapshot.provider_location)
|
||||
|
||||
@mock.patch.object(cnfs.VeritasCNFSDriver, '_ensure_share_mounted')
|
||||
@mock.patch.object(cnfs.VeritasCNFSDriver, 'local_path')
|
||||
def test_delete_snapshot(self, m_local_path, m_ensure_share_mounted):
|
||||
"""test delete snapshot"""
|
||||
drv = self.driver
|
||||
snapshot = fake_snapshot.fake_snapshot_obj(context.get_admin_context(),
|
||||
provider_location=self._loc)
|
||||
m_ensure_share_mounted(self._loc).AndReturn(None)
|
||||
m_local_path(snapshot).AndReturn(self.TEST_SNAP_LOCAL_PATH)
|
||||
with mock.patch.object(drv, '_execute'):
|
||||
drv.delete_snapshot(snapshot)
|
||||
|
||||
@mock.patch.object(cnfs.VeritasCNFSDriver, '_do_clone_volume')
|
||||
@mock.patch.object(cnfs.VeritasCNFSDriver, 'local_path')
|
||||
def test_create_volume_from_snapshot_greater_size(self, m_local_path,
|
||||
m_do_clone_volume):
|
||||
"""test create volume from snapshot with greater volume size"""
|
||||
drv = self.driver
|
||||
volume = fake_volume.fake_volume_obj(self.context)
|
||||
snapshot = fake_volume.fake_volume_obj(self.context,
|
||||
provider_location=self._loc)
|
||||
volume.size = 20
|
||||
snapshot.volume_size = 10
|
||||
m_do_clone_volume(snapshot, snapshot.name,
|
||||
volume).return_value = True
|
||||
m_local_path(volume).AndReturn(self.TEST_VOL_LOCAL_PATH)
|
||||
with mock.patch.object(drv, '_execute'):
|
||||
drv.create_volume_from_snapshot(volume, snapshot)
|
||||
self.assertEqual(volume.provider_location, snapshot.provider_location)
|
180
cinder/volume/drivers/veritas_cnfs.py
Normal file
180
cinder/volume/drivers/veritas_cnfs.py
Normal file
@ -0,0 +1,180 @@
|
||||
# Copyright (c) 2017 Veritas Technologies LLC
|
||||
# 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 os
|
||||
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import excutils
|
||||
|
||||
from cinder import exception
|
||||
from cinder.i18n import _
|
||||
from cinder import interface
|
||||
from cinder.volume.drivers import nfs
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@interface.volumedriver
|
||||
class VeritasCNFSDriver(nfs.NfsDriver):
|
||||
|
||||
"""Veritas Clustered NFS based cinder driver
|
||||
|
||||
1.0.0 - Initial driver implementations for Kilo.
|
||||
1.0.1 - Liberty release driver not implemented.
|
||||
Place holder for Liberty release in case we
|
||||
need to support.
|
||||
1.0.2 - cinder.interface.volumedriver decorator.
|
||||
Mitaka/Newton/Okata Release
|
||||
1.0.3 - Seperate create_cloned_volume() and
|
||||
create_volume_from_snapshot () functionality.
|
||||
Pike Release
|
||||
Executes commands relating to Volumes.
|
||||
"""
|
||||
|
||||
VERSION = "1.0.3"
|
||||
# ThirdPartySytems wiki page
|
||||
CI_WIKI_NAME = "Veritas_Access_CI"
|
||||
DRIVER_VOLUME_TYPE = 'nfs'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._execute = None
|
||||
self._context = None
|
||||
super(VeritasCNFSDriver, self).__init__(*args, **kwargs)
|
||||
|
||||
def do_setup(self, context):
|
||||
self._context = context
|
||||
super(VeritasCNFSDriver, self).do_setup(context)
|
||||
opts = self.configuration.nfs_mount_options
|
||||
if not opts or opts.find('vers=3') == -1 or (
|
||||
opts.find('nfsvers=3')) == -1:
|
||||
msg = _("NFS is not configured to use NFSv3")
|
||||
LOG.error(msg)
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
|
||||
def create_volume_from_snapshot(self, volume, snapshot):
|
||||
"""Creates a volume from snapshot."""
|
||||
LOG.debug('VeritasNFSDriver create_volume_from_snapshot called '
|
||||
'volume_id = %(volume)s and snapshot_id = %(snapshot)s',
|
||||
{'volume': volume.id, 'snapshot': snapshot.id})
|
||||
snap_name = snapshot.name
|
||||
vol_size = volume.size
|
||||
snap_size = snapshot.volume_size
|
||||
self._do_clone_volume(snapshot, snap_name, volume)
|
||||
volume.provider_location = snapshot.provider_location
|
||||
|
||||
if vol_size != snap_size:
|
||||
try:
|
||||
self.extend_volume(volume, vol_size)
|
||||
except exception.ExtendVolumeError as ex:
|
||||
with excutils.save_and_reraise_exception():
|
||||
LOG.error('Failed to extend Volume: %s', ex.msg)
|
||||
path = self.local_path(volume)
|
||||
self._delete_file(path)
|
||||
return {'provider_location': volume.provider_location}
|
||||
|
||||
def _get_vol_by_id(self, volid):
|
||||
vol = self.db.volume_get(self._context, volid)
|
||||
return vol
|
||||
|
||||
def _delete_file(self, path):
|
||||
"""Deletes file from disk and return result as boolean."""
|
||||
try:
|
||||
LOG.debug('Deleting file at path %s', path)
|
||||
self._execute('rm', '-f', path, run_as_root=True)
|
||||
except OSError as ex:
|
||||
LOG.warning('Exception during deleting %s', ex.strerror)
|
||||
|
||||
def create_snapshot(self, snapshot):
|
||||
"""Create a snapshot of the volume."""
|
||||
src_vol_id = snapshot.volume_id
|
||||
src_vol_name = snapshot.volume_name
|
||||
src_vol = self._get_vol_by_id(src_vol_id)
|
||||
self._do_clone_volume(src_vol, src_vol_name, snapshot)
|
||||
snapshot.provider_location = src_vol.provider_location
|
||||
LOG.debug("VeritasNFSDriver create_snapshot %r",
|
||||
snapshot.provider_location)
|
||||
return {'provider_location': snapshot.provider_location}
|
||||
|
||||
def delete_snapshot(self, snapshot):
|
||||
"""Delete a snapshot."""
|
||||
if not snapshot.provider_location:
|
||||
LOG.warning('Snapshot %s does not have provider_location '
|
||||
'specified, skipping', snapshot.name)
|
||||
return
|
||||
self._ensure_share_mounted(snapshot.provider_location)
|
||||
snap_path = self.local_path(snapshot)
|
||||
self._delete_file(snap_path)
|
||||
|
||||
def create_cloned_volume(self, volume, src_vref):
|
||||
"""Create a clone of the volume."""
|
||||
|
||||
LOG.debug('VeritasNFSDriver create_cloned_volume called '
|
||||
'volume_id = %(volume)s and src_vol_id = %(src_vol_id)s',
|
||||
{'volume': volume.id, 'src_vol_id': src_vref.id})
|
||||
src_vol_name = src_vref.name
|
||||
vol_size = volume.size
|
||||
src_vol_size = src_vref.size
|
||||
self._do_clone_volume(src_vref, src_vol_name, volume)
|
||||
volume.provider_location = src_vref.provider_location
|
||||
|
||||
if vol_size != src_vol_size:
|
||||
try:
|
||||
self.extend_volume(volume, vol_size)
|
||||
except exception.ExtendVolumeError as ex:
|
||||
with excutils.save_and_reraise_exception():
|
||||
LOG.error('Failed to extend Volume: %s', ex.msg)
|
||||
path = self.local_path(volume)
|
||||
self._delete_file(path)
|
||||
return {'provider_location': volume.provider_location}
|
||||
|
||||
def _get_local_volume_path(self, provider_loc, vol_name):
|
||||
mnt_path = self._get_mount_point_for_share(provider_loc)
|
||||
vol_path = os.path.join(mnt_path, vol_name)
|
||||
return vol_path
|
||||
|
||||
def _do_clone_volume(self, src_vol, src_vol_name, tgt_vol):
|
||||
cnfs_share = src_vol.provider_location
|
||||
tgt_vol_name = tgt_vol.name
|
||||
tgt_vol_path = self._get_local_volume_path(cnfs_share, tgt_vol_name)
|
||||
src_vol_path = self._get_local_volume_path(cnfs_share, src_vol_name)
|
||||
tgt_vol_path_spl = tgt_vol_path + "::snap:vxfs:"
|
||||
self._execute('ln', src_vol_path, tgt_vol_path_spl, run_as_root=True)
|
||||
LOG.debug("VeritasNFSDriver: do_clone_volume %(src_vol_path)s "
|
||||
"%(tgt_vol_path)s %(tgt_vol_path_spl)s",
|
||||
{'src_vol_path': src_vol_path,
|
||||
'tgt_vol_path_spl': tgt_vol_path_spl,
|
||||
'tgt_vol_path': tgt_vol_path})
|
||||
if not os.path.exists(tgt_vol_path):
|
||||
self._execute('rm', '-f', tgt_vol_path_spl, run_as_root=True)
|
||||
msg = _("Filesnap over NFS is not supported, "
|
||||
"removing the ::snap:vxfs: file")
|
||||
LOG.error(msg)
|
||||
raise exception.NfsException(msg)
|
||||
|
||||
def extend_volume(self, volume, size):
|
||||
"""Extend the volume to new size"""
|
||||
path = self.local_path(volume)
|
||||
self._execute('truncate', '-s', '%sG' % size, path, run_as_root=True)
|
||||
LOG.debug("VeritasNFSDriver: extend_volume volume_id = %s", volume.id)
|
||||
|
||||
def _update_volume_stats(self):
|
||||
super(VeritasCNFSDriver, self)._update_volume_stats()
|
||||
backend_name = self.configuration.safe_get('volume_backend_name')
|
||||
res_percentage = self.configuration.safe_get('reserved_percentage')
|
||||
self._stats["volume_backend_name"] = backend_name or 'VeritasCNFS'
|
||||
self._stats["vendor_name"] = 'Veritas'
|
||||
self._stats["reserved_percentage"] = res_percentage or 0
|
||||
self._stats["driver_version"] = self.VERSION
|
||||
self._stats["storage_protocol"] = self.DRIVER_VOLUME_TYPE
|
@ -0,0 +1,3 @@
|
||||
---
|
||||
features:
|
||||
- Added NFS based driver for Veritas Access.
|
Loading…
x
Reference in New Issue
Block a user