Remove the deprecated ibmnas driver
The ibmnas driver has been deprecated since the Liberty release.
In the Liberty release, NFS capabilities were added to the IBM
GPFS driver with commit: bf4ca3bcf0
.
The ability for the GPFS driver to use NFS based shares rendered
the ibmnas driver redundant.
This commit removes the ibmnas driver and its tests. The commit also
updates the opts.py file to remove reference to the ibmnas options.
Release notes are included with this commit.
DocImpact
Change-Id: Iac4a2f1770f7d02979eec1d8d5866d9fc4b6d18c
This commit is contained in:
parent
94d4b15943
commit
f63d321774
@ -109,8 +109,6 @@ from cinder.volume.drivers.ibm import flashsystem_fc as \
|
||||
from cinder.volume.drivers.ibm import flashsystem_iscsi as \
|
||||
cinder_volume_drivers_ibm_flashsystemiscsi
|
||||
from cinder.volume.drivers.ibm import gpfs as cinder_volume_drivers_ibm_gpfs
|
||||
from cinder.volume.drivers.ibm import ibmnas as \
|
||||
cinder_volume_drivers_ibm_ibmnas
|
||||
from cinder.volume.drivers.ibm.storwize_svc import storwize_svc_common as \
|
||||
cinder_volume_drivers_ibm_storwize_svc_storwizesvccommon
|
||||
from cinder.volume.drivers.ibm.storwize_svc import storwize_svc_fc as \
|
||||
@ -226,7 +224,6 @@ def list_opts():
|
||||
cinder_volume_drivers_sheepdog.sheepdog_opts,
|
||||
[cinder_api_middleware_sizelimit.max_request_body_size_opt],
|
||||
cinder_volume_drivers_solidfire.sf_opts,
|
||||
cinder_volume_drivers_ibm_ibmnas.platform_opts,
|
||||
cinder_backup_drivers_swift.swiftbackup_service_opts,
|
||||
cinder_volume_drivers_cloudbyte_options.
|
||||
cloudbyte_add_qosgroup_opts,
|
||||
|
@ -1,490 +0,0 @@
|
||||
# Copyright 2013 IBM Corp.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# Authors:
|
||||
# Nilesh Bhosale <nilesh.bhosale@in.ibm.com>
|
||||
# Sasikanth Eda <sasikanth.eda@in.ibm.com>
|
||||
|
||||
"""
|
||||
Tests for the IBM NAS family (SONAS, Storwize V7000 Unified,
|
||||
NAS based IBM GPFS Storage Systems).
|
||||
"""
|
||||
|
||||
import mock
|
||||
from oslo_config import cfg
|
||||
from oslo_utils import units
|
||||
|
||||
from cinder import context
|
||||
from cinder import exception
|
||||
from cinder import test
|
||||
from cinder.volume import configuration as conf
|
||||
from cinder.volume.drivers.ibm import ibmnas
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class FakeEnv(object):
|
||||
fields = {}
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self.fields[key] = value
|
||||
|
||||
def __getitem__(self, item):
|
||||
return self.fields[item]
|
||||
|
||||
|
||||
class IBMNASDriverTestCase(test.TestCase):
|
||||
|
||||
TEST_NFS_EXPORT = 'nfs-host1:/export'
|
||||
TEST_SIZE_IN_GB = 1
|
||||
TEST_EXTEND_SIZE_IN_GB = 2
|
||||
TEST_MNT_POINT = '/mnt/nfs'
|
||||
TEST_MNT_POINT_BASE = '/mnt'
|
||||
TEST_LOCAL_PATH = '/mnt/nfs/volume-123'
|
||||
TEST_VOLUME_PATH = '/export/volume-123'
|
||||
TEST_SNAP_PATH = '/export/snapshot-123'
|
||||
|
||||
def setUp(self):
|
||||
super(IBMNASDriverTestCase, self).setUp()
|
||||
self._driver = ibmnas.IBMNAS_NFSDriver(configuration=
|
||||
conf.Configuration(None))
|
||||
self._mock = mock.Mock()
|
||||
self._def_flags = {'nas_ip': 'hostname',
|
||||
'nas_login': 'user',
|
||||
'nas_ssh_port': 22,
|
||||
'nas_password': 'pass',
|
||||
'nas_private_key': 'nas.key',
|
||||
'ibmnas_platform_type': 'v7ku',
|
||||
'nfs_shares_config': None,
|
||||
'nfs_sparsed_volumes': True,
|
||||
'nfs_used_ratio': 0.95,
|
||||
'nfs_oversub_ratio': 1.0,
|
||||
'nfs_mount_point_base':
|
||||
self.TEST_MNT_POINT_BASE,
|
||||
'nfs_mount_options': None}
|
||||
|
||||
self.context = context.get_admin_context()
|
||||
self.context.user_id = 'fake'
|
||||
self.context.project_id = 'fake'
|
||||
|
||||
def _set_flag(self, flag, value):
|
||||
group = self._driver.configuration.config_group
|
||||
self._driver.configuration.set_override(flag, value, group)
|
||||
|
||||
def _reset_flags(self):
|
||||
self._driver.configuration.local_conf.reset()
|
||||
for k, v in self._def_flags.items():
|
||||
self._set_flag(k, v)
|
||||
|
||||
def test_check_for_setup_error(self):
|
||||
"""Check setup with bad parameters."""
|
||||
|
||||
drv = self._driver
|
||||
|
||||
required_flags = [
|
||||
'nas_ip',
|
||||
'nas_login',
|
||||
'nas_ssh_port']
|
||||
|
||||
for flag in required_flags:
|
||||
self._set_flag(flag, None)
|
||||
self.assertRaises(exception.CinderException,
|
||||
drv.check_for_setup_error)
|
||||
|
||||
self._set_flag('nas_password', None)
|
||||
self._set_flag('nas_private_key', None)
|
||||
self.assertRaises(exception.InvalidInput,
|
||||
self._driver.check_for_setup_error)
|
||||
self._set_flag('ibmnas_platform_type', None)
|
||||
self.assertRaises(exception.InvalidInput,
|
||||
self._driver.check_for_setup_error)
|
||||
|
||||
self._reset_flags()
|
||||
|
||||
def test_get_provider_location(self):
|
||||
"""Check provider location for given volume id."""
|
||||
|
||||
mock = self._mock
|
||||
|
||||
volume = FakeEnv()
|
||||
volume['id'] = '123'
|
||||
|
||||
mock.drv._get_provider_location.return_value = self.TEST_NFS_EXPORT
|
||||
self.assertEqual(self.TEST_NFS_EXPORT,
|
||||
mock.drv._get_provider_location(volume['id']))
|
||||
|
||||
def test_get_export_path(self):
|
||||
"""Check export path for the given volume."""
|
||||
|
||||
mock = self._mock
|
||||
|
||||
volume = FakeEnv()
|
||||
volume['id'] = '123'
|
||||
|
||||
mock.drv._get_export_path.return_value = self.TEST_NFS_EXPORT.\
|
||||
split(':')[1]
|
||||
self.assertEqual(self.TEST_NFS_EXPORT.split(':')[1],
|
||||
mock.drv._get_export_path(volume['id']))
|
||||
|
||||
@mock.patch('cinder.volume.drivers.ibm.ibmnas.IBMNAS_NFSDriver.'
|
||||
'_ensure_shares_mounted')
|
||||
def test_update_volume_stats(self, mock_ensure):
|
||||
"""Check update volume stats."""
|
||||
|
||||
drv = self._driver
|
||||
mock_ensure.return_value = True
|
||||
fake_avail = 80 * units.Gi
|
||||
fake_size = 2 * fake_avail
|
||||
fake_used = 10 * units.Gi
|
||||
|
||||
with mock.patch('cinder.volume.drivers.ibm.ibmnas.IBMNAS_NFSDriver.'
|
||||
'_get_capacity_info',
|
||||
return_value=(fake_avail, fake_size, fake_used)):
|
||||
stats = drv.get_volume_stats()
|
||||
self.assertEqual('IBMNAS_NFS', stats['volume_backend_name'])
|
||||
self.assertEqual('nfs', stats['storage_protocol'])
|
||||
self.assertEqual('1.1.0', stats['driver_version'])
|
||||
self.assertEqual('IBM', stats['vendor_name'])
|
||||
|
||||
@mock.patch('cinder.volume.drivers.ibm.ibmnas.IBMNAS_NFSDriver._run_ssh')
|
||||
def test_ssh_operation(self, mock_ssh):
|
||||
|
||||
drv = self._driver
|
||||
mock_ssh.return_value = None
|
||||
|
||||
self.assertIsNone(drv._ssh_operation('ssh_cmd'))
|
||||
|
||||
@mock.patch('cinder.volume.drivers.ibm.ibmnas.IBMNAS_NFSDriver._run_ssh')
|
||||
def test_ssh_operation_exception(self, mock_ssh):
|
||||
|
||||
drv = self._driver
|
||||
mock_ssh.side_effect = (
|
||||
exception.VolumeBackendAPIException(data='Failed'))
|
||||
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
drv._ssh_operation, 'ssh_cmd')
|
||||
|
||||
@mock.patch('cinder.volume.drivers.ibm.ibmnas.IBMNAS_NFSDriver.'
|
||||
'_ssh_operation')
|
||||
@mock.patch('oslo_concurrency.processutils.execute')
|
||||
def test_create_ibmnas_snap_mount_point_provided(self, mock_ssh,
|
||||
mock_execute):
|
||||
"""Create ibmnas snap if mount point is provided."""
|
||||
|
||||
drv = self._driver
|
||||
mock_ssh.return_value = True
|
||||
mock_execute.return_value = True
|
||||
|
||||
self.assertIsNone(drv._create_ibmnas_snap(self.TEST_VOLUME_PATH,
|
||||
self.TEST_SNAP_PATH,
|
||||
self.TEST_MNT_POINT))
|
||||
|
||||
@mock.patch('cinder.volume.drivers.ibm.ibmnas.IBMNAS_NFSDriver.'
|
||||
'_ssh_operation')
|
||||
@mock.patch('oslo_concurrency.processutils.execute')
|
||||
def test_create_ibmnas_snap_nas_gpfs(self, mock_execute, mock_ssh):
|
||||
"""Create ibmnas snap if mount point is provided."""
|
||||
|
||||
drv = self._driver
|
||||
drv.configuration.platform = 'gpfs-nas'
|
||||
mock_ssh.return_value = True
|
||||
mock_execute.return_value = True
|
||||
|
||||
self.assertIsNone(drv._create_ibmnas_snap(self.TEST_VOLUME_PATH,
|
||||
self.TEST_SNAP_PATH,
|
||||
self.TEST_MNT_POINT))
|
||||
|
||||
@mock.patch('cinder.volume.drivers.ibm.ibmnas.IBMNAS_NFSDriver.'
|
||||
'_ssh_operation')
|
||||
def test_create_ibmnas_snap_no_mount_point_provided(self, mock_ssh):
|
||||
"""Create ibmnas snap if no mount point is provided."""
|
||||
|
||||
drv = self._driver
|
||||
mock_ssh.return_value = True
|
||||
|
||||
self.assertIsNone(drv._create_ibmnas_snap(self.TEST_VOLUME_PATH,
|
||||
self.TEST_SNAP_PATH,
|
||||
None))
|
||||
|
||||
@mock.patch('cinder.volume.drivers.ibm.ibmnas.IBMNAS_NFSDriver.'
|
||||
'_ssh_operation')
|
||||
def test_create_ibmnas_snap_nas_gpfs_no_mount(self, mock_ssh):
|
||||
"""Create ibmnas snap (gpfs-nas) if mount point is provided."""
|
||||
|
||||
drv = self._driver
|
||||
drv.configuration.platform = 'gpfs-nas'
|
||||
mock_ssh.return_value = True
|
||||
|
||||
drv._create_ibmnas_snap(self.TEST_VOLUME_PATH,
|
||||
self.TEST_SNAP_PATH, None)
|
||||
|
||||
@mock.patch('cinder.volume.drivers.ibm.ibmnas.IBMNAS_NFSDriver.'
|
||||
'_ssh_operation')
|
||||
def test_create_ibmnas_copy(self, mock_ssh):
|
||||
"""Create ibmnas copy test case."""
|
||||
|
||||
drv = self._driver
|
||||
TEST_DEST_SNAP = '/export/snapshot-123.snap'
|
||||
TEST_DEST_PATH = '/export/snapshot-123'
|
||||
mock_ssh.return_value = True
|
||||
|
||||
drv._create_ibmnas_copy(self.TEST_VOLUME_PATH,
|
||||
TEST_DEST_PATH,
|
||||
TEST_DEST_SNAP)
|
||||
|
||||
@mock.patch('cinder.volume.drivers.ibm.ibmnas.IBMNAS_NFSDriver.'
|
||||
'_ssh_operation')
|
||||
def test_create_ibmnas_copy_nas_gpfs(self, mock_ssh):
|
||||
"""Create ibmnas copy for gpfs-nas platform test case."""
|
||||
|
||||
drv = self._driver
|
||||
TEST_DEST_SNAP = '/export/snapshot-123.snap'
|
||||
TEST_DEST_PATH = '/export/snapshot-123'
|
||||
drv.configuration.platform = 'gpfs-nas'
|
||||
mock_ssh.return_value = True
|
||||
|
||||
drv._create_ibmnas_copy(self.TEST_VOLUME_PATH,
|
||||
TEST_DEST_PATH,
|
||||
TEST_DEST_SNAP)
|
||||
|
||||
@mock.patch('cinder.image.image_utils.resize_image')
|
||||
def test_resize_volume_file(self, mock_size):
|
||||
"""Resize volume file test case."""
|
||||
|
||||
drv = self._driver
|
||||
mock_size.return_value = True
|
||||
|
||||
self.assertTrue(drv._resize_volume_file(self.TEST_LOCAL_PATH,
|
||||
self.TEST_EXTEND_SIZE_IN_GB))
|
||||
|
||||
@mock.patch('cinder.image.image_utils.resize_image')
|
||||
def test_resize_volume_exception(self, mock_size):
|
||||
"""Resize volume file test case."""
|
||||
|
||||
drv = self._driver
|
||||
mock_size.side_effect = (
|
||||
exception.VolumeBackendAPIException(data='Failed'))
|
||||
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
drv._resize_volume_file,
|
||||
self.TEST_LOCAL_PATH,
|
||||
self.TEST_EXTEND_SIZE_IN_GB)
|
||||
|
||||
@mock.patch('cinder.volume.drivers.ibm.ibmnas.IBMNAS_NFSDriver.local_path')
|
||||
@mock.patch('cinder.volume.drivers.ibm.ibmnas.IBMNAS_NFSDriver.'
|
||||
'_resize_volume_file')
|
||||
def test_extend_volume(self, mock_resize, mock_local):
|
||||
"""Extend volume to greater size test case."""
|
||||
|
||||
drv = self._driver
|
||||
mock_local.return_value = self.TEST_LOCAL_PATH
|
||||
mock_resize.return_value = True
|
||||
volume = FakeEnv()
|
||||
volume['name'] = 'vol-123'
|
||||
|
||||
drv.extend_volume(volume,
|
||||
self.TEST_EXTEND_SIZE_IN_GB)
|
||||
|
||||
@mock.patch('cinder.volume.drivers.ibm.ibmnas.IBMNAS_NFSDriver._run_ssh')
|
||||
@mock.patch('oslo_concurrency.processutils.execute')
|
||||
def test_delete_snapfiles(self, mock_execute, mock_ssh):
|
||||
"""Delete_snapfiles test case."""
|
||||
|
||||
drv = self._driver
|
||||
expected = ('Parent Depth Parent inode'
|
||||
'File name\n yes 0 /ibm/gpfs0/gshare/\n'
|
||||
'volume-123\n EFSSG1000I The command'
|
||||
'completed successfully.', '')
|
||||
mock_ssh.return_value = expected
|
||||
mock_execute.return_value = expected
|
||||
|
||||
drv._delete_snapfiles(self.TEST_VOLUME_PATH,
|
||||
self.TEST_MNT_POINT)
|
||||
|
||||
@mock.patch('cinder.volume.drivers.ibm.ibmnas.IBMNAS_NFSDriver._run_ssh')
|
||||
@mock.patch('oslo_concurrency.processutils.execute')
|
||||
def test_delete_snapfiles_nas_gpfs(self, mock_execute, mock_ssh):
|
||||
"""Delete_snapfiles for gpfs-nas platform test case."""
|
||||
|
||||
drv = self._driver
|
||||
drv.configuration.platform = 'gpfs-nas'
|
||||
expected = ('Parent Depth Parent inode'
|
||||
'File name\n'
|
||||
'------ ----- -------------'
|
||||
'- ---------\n'
|
||||
'yes 0\n'
|
||||
'/ibm/gpfs0/gshare/volume-123', '')
|
||||
mock_ssh.return_value = expected
|
||||
mock_execute.return_value = expected
|
||||
|
||||
drv._delete_snapfiles(self.TEST_VOLUME_PATH,
|
||||
self.TEST_MNT_POINT)
|
||||
|
||||
def test_delete_volume_no_provider_location(self):
|
||||
"""Delete volume with no provider location specified."""
|
||||
|
||||
drv = self._driver
|
||||
|
||||
volume = FakeEnv()
|
||||
volume['name'] = 'volume-123'
|
||||
volume['provider_location'] = None
|
||||
|
||||
result = drv.delete_volume(volume)
|
||||
self.assertIsNone(result)
|
||||
|
||||
@mock.patch('cinder.volume.drivers.ibm.ibmnas.IBMNAS_NFSDriver.'
|
||||
'_get_export_path')
|
||||
@mock.patch('cinder.volume.drivers.ibm.ibmnas.IBMNAS_NFSDriver.'
|
||||
'_delete_snapfiles')
|
||||
def test_delete_volume(self, mock_snap, mock_export):
|
||||
"""Delete volume test case."""
|
||||
|
||||
drv = self._driver
|
||||
mock_export.return_value = self.TEST_VOLUME_PATH
|
||||
mock_snap.return_value = True
|
||||
|
||||
volume = FakeEnv()
|
||||
volume['id'] = '123'
|
||||
volume['name'] = '/volume-123'
|
||||
volume['provider_location'] = self.TEST_VOLUME_PATH
|
||||
|
||||
self.assertIsNone(drv.delete_volume(volume))
|
||||
|
||||
@mock.patch('cinder.volume.drivers.ibm.ibmnas.IBMNAS_NFSDriver.'
|
||||
'_get_export_path')
|
||||
@mock.patch('cinder.volume.drivers.ibm.ibmnas.IBMNAS_NFSDriver.'
|
||||
'_get_provider_location')
|
||||
@mock.patch('cinder.volume.drivers.ibm.ibmnas.IBMNAS_NFSDriver.'
|
||||
'_get_mount_point_for_share')
|
||||
@mock.patch('cinder.volume.drivers.ibm.ibmnas.IBMNAS_NFSDriver.'
|
||||
'_create_ibmnas_snap')
|
||||
def test_create_snapshot(self, mock_snap, mock_mount, mock_provider,
|
||||
mock_export):
|
||||
"""Create snapshot simple test case."""
|
||||
|
||||
drv = self._driver
|
||||
mock_export.return_value = self.TEST_LOCAL_PATH
|
||||
mock_provider.return_value = self.TEST_VOLUME_PATH
|
||||
mock_mount.return_value = self.TEST_MNT_POINT
|
||||
mock_snap.return_value = True
|
||||
|
||||
volume = FakeEnv()
|
||||
volume['id'] = '123'
|
||||
volume['name'] = 'volume-123'
|
||||
|
||||
snapshot = FakeEnv()
|
||||
snapshot['volume_id'] = volume['id']
|
||||
snapshot['volume_name'] = '/volume-123'
|
||||
snapshot['name'] = '/snapshot-123'
|
||||
|
||||
drv.create_snapshot(snapshot)
|
||||
|
||||
@mock.patch('cinder.volume.drivers.ibm.ibmnas.IBMNAS_NFSDriver.'
|
||||
'_get_provider_location')
|
||||
@mock.patch('cinder.volume.drivers.ibm.ibmnas.IBMNAS_NFSDriver.'
|
||||
'_get_mount_point_for_share')
|
||||
@mock.patch('oslo_concurrency.processutils.execute')
|
||||
def test_delete_snapshot(self, mock_execute, mock_mount, mock_provider):
|
||||
"""Delete snapshot simple test case."""
|
||||
|
||||
drv = self._driver
|
||||
mock_provider.return_value = self.TEST_VOLUME_PATH
|
||||
mock_mount.return_value = self.TEST_LOCAL_PATH
|
||||
mock_execute.return_value = True
|
||||
|
||||
volume = FakeEnv()
|
||||
volume['id'] = '123'
|
||||
volume['provider_location'] = self.TEST_NFS_EXPORT
|
||||
|
||||
snapshot = FakeEnv()
|
||||
snapshot['volume_id'] = volume['id']
|
||||
snapshot['volume_name'] = 'volume-123'
|
||||
snapshot['name'] = 'snapshot-123'
|
||||
|
||||
drv.delete_snapshot(snapshot)
|
||||
|
||||
@mock.patch('cinder.volume.drivers.ibm.ibmnas.IBMNAS_NFSDriver.'
|
||||
'_get_export_path')
|
||||
@mock.patch('cinder.volume.drivers.ibm.ibmnas.IBMNAS_NFSDriver.'
|
||||
'_create_ibmnas_copy')
|
||||
@mock.patch('cinder.volume.drivers.ibm.ibmnas.IBMNAS_NFSDriver.'
|
||||
'_find_share')
|
||||
@mock.patch('cinder.volume.drivers.ibm.ibmnas.IBMNAS_NFSDriver.local_path')
|
||||
@mock.patch('cinder.volume.drivers.ibm.ibmnas.IBMNAS_NFSDriver.'
|
||||
'_set_rw_permissions_for_owner')
|
||||
@mock.patch('cinder.volume.drivers.ibm.ibmnas.IBMNAS_NFSDriver.'
|
||||
'_resize_volume_file')
|
||||
def test_create_cloned_volume(self, mock_resize, mock_rw, mock_local,
|
||||
mock_find, mock_copy, mock_export):
|
||||
"""Clone volume with equal size test case."""
|
||||
|
||||
drv = self._driver
|
||||
mock_export.return_value = self.TEST_VOLUME_PATH
|
||||
mock_copy.return_value = True
|
||||
mock_find.return_value = self.TEST_LOCAL_PATH
|
||||
mock_local.return_value = self.TEST_LOCAL_PATH
|
||||
mock_rw.return_value = True
|
||||
mock_resize.return_value = True
|
||||
|
||||
volume_src = FakeEnv()
|
||||
volume_src['id'] = '123'
|
||||
volume_src['name'] = '/volume-123'
|
||||
volume_src.size = self.TEST_SIZE_IN_GB
|
||||
|
||||
volume_dest = FakeEnv()
|
||||
volume_dest['id'] = '456'
|
||||
volume_dest['name'] = '/volume-456'
|
||||
volume_dest['size'] = self.TEST_SIZE_IN_GB
|
||||
volume_dest.size = self.TEST_SIZE_IN_GB
|
||||
|
||||
self.assertEqual({'provider_location': self.TEST_LOCAL_PATH},
|
||||
drv.create_cloned_volume(volume_dest, volume_src))
|
||||
|
||||
@mock.patch('cinder.volume.drivers.ibm.ibmnas.IBMNAS_NFSDriver.'
|
||||
'_get_export_path')
|
||||
@mock.patch('cinder.volume.drivers.ibm.ibmnas.IBMNAS_NFSDriver.'
|
||||
'_create_ibmnas_snap')
|
||||
@mock.patch('cinder.volume.drivers.ibm.ibmnas.IBMNAS_NFSDriver.'
|
||||
'_find_share')
|
||||
@mock.patch('cinder.volume.drivers.ibm.ibmnas.IBMNAS_NFSDriver.local_path')
|
||||
@mock.patch('cinder.volume.drivers.ibm.ibmnas.IBMNAS_NFSDriver.'
|
||||
'_set_rw_permissions_for_owner')
|
||||
@mock.patch('cinder.volume.drivers.ibm.ibmnas.IBMNAS_NFSDriver.'
|
||||
'_resize_volume_file')
|
||||
def test_create_volume_from_snapshot(self, mock_resize, mock_rw,
|
||||
mock_local, mock_find, mock_snap,
|
||||
mock_export):
|
||||
"""Create volume from snapshot test case."""
|
||||
|
||||
drv = self._driver
|
||||
mock_export.return_value = '/export'
|
||||
mock_snap.return_value = self.TEST_LOCAL_PATH
|
||||
mock_find.return_value = self.TEST_LOCAL_PATH
|
||||
mock_local.return_value = self.TEST_VOLUME_PATH
|
||||
mock_rw.return_value = True
|
||||
mock_resize.return_value = True
|
||||
|
||||
volume = FakeEnv()
|
||||
volume['id'] = '123'
|
||||
volume['name'] = '/volume-123'
|
||||
volume['size'] = self.TEST_SIZE_IN_GB
|
||||
|
||||
snapshot = FakeEnv()
|
||||
snapshot['volume_id'] = volume['id']
|
||||
snapshot['volume_name'] = 'volume-123'
|
||||
snapshot['volume_size'] = self.TEST_SIZE_IN_GB
|
||||
snapshot.name = '/snapshot-123'
|
||||
|
||||
self.assertEqual({'provider_location': self.TEST_LOCAL_PATH},
|
||||
drv.create_volume_from_snapshot(volume, snapshot))
|
@ -1,376 +0,0 @@
|
||||
# Copyright 2014 IBM Corp.
|
||||
#
|
||||
# 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.
|
||||
# Authors:
|
||||
# Nilesh Bhosale <nilesh.bhosale@in.ibm.com>
|
||||
# Sasikanth Eda <sasikanth.eda@in.ibm.com>
|
||||
|
||||
"""
|
||||
IBM NAS Volume Driver.
|
||||
Currently, it supports the following IBM Storage Systems:
|
||||
1. IBM Scale Out NAS (SONAS)
|
||||
2. IBM Storwize V7000 Unified
|
||||
3. NAS based IBM GPFS Storage Systems
|
||||
|
||||
Notes:
|
||||
1. If you specify both a password and a key file, this driver will use the
|
||||
key file only.
|
||||
2. When using a key file for authentication, it is up to the user or
|
||||
system administrator to store the private key in a safe manner.
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
|
||||
from oslo_concurrency import processutils
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import units
|
||||
import six
|
||||
|
||||
from cinder import exception
|
||||
from cinder.i18n import _, _LI, _LW
|
||||
from cinder.image import image_utils
|
||||
from cinder import utils
|
||||
from cinder.volume.drivers import nfs
|
||||
from cinder.volume.drivers import remotefs
|
||||
from cinder.volume.drivers.san import san
|
||||
|
||||
VERSION = '1.1.0'
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
platform_opts = [
|
||||
cfg.StrOpt('ibmnas_platform_type',
|
||||
default='v7ku',
|
||||
choices=['v7ku', 'sonas', 'gpfs-nas'],
|
||||
help=('IBMNAS platform type to be used as backend storage; '
|
||||
'valid values are - '
|
||||
'v7ku : for using IBM Storwize V7000 Unified, '
|
||||
'sonas : for using IBM Scale Out NAS, '
|
||||
'gpfs-nas : for using NFS based IBM GPFS deployments.')),
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.register_opts(platform_opts)
|
||||
|
||||
|
||||
class IBMNAS_NFSDriver(nfs.NfsDriver, san.SanDriver):
|
||||
"""IBMNAS NFS based cinder driver.
|
||||
|
||||
Creates file on NFS share for using it as block device on hypervisor.
|
||||
Version history:
|
||||
1.0.0 - Initial driver
|
||||
1.1.0 - Support for NFS based GPFS storage backend
|
||||
"""
|
||||
|
||||
driver_volume_type = 'nfs'
|
||||
VERSION = VERSION
|
||||
|
||||
def __init__(self, execute=utils.execute, *args, **kwargs):
|
||||
self._context = None
|
||||
super(IBMNAS_NFSDriver, self).__init__(*args, **kwargs)
|
||||
self.configuration.append_config_values(remotefs.nas_opts)
|
||||
self.configuration.append_config_values(platform_opts)
|
||||
self.configuration.san_ip = self.configuration.nas_ip
|
||||
self.configuration.san_login = self.configuration.nas_login
|
||||
self.configuration.san_password = self.configuration.nas_password
|
||||
self.configuration.san_private_key = \
|
||||
self.configuration.nas_private_key
|
||||
self.configuration.san_ssh_port = self.configuration.nas_ssh_port
|
||||
self.configuration.ibmnas_platform_type = \
|
||||
self.configuration.ibmnas_platform_type.lower()
|
||||
self._execute = execute
|
||||
LOG.info(_LI('Initialized driver for IBMNAS Platform: %s.'),
|
||||
self.configuration.ibmnas_platform_type)
|
||||
|
||||
def do_setup(self, context):
|
||||
"""Any initialization the volume driver does while starting."""
|
||||
super(IBMNAS_NFSDriver, self).do_setup(context)
|
||||
self._context = context
|
||||
|
||||
def check_for_setup_error(self):
|
||||
"""Ensure that the flags are set properly."""
|
||||
required_flags = ['nas_ip', 'nas_ssh_port', 'nas_login',
|
||||
'ibmnas_platform_type']
|
||||
valid_platforms = ['v7ku', 'sonas', 'gpfs-nas']
|
||||
|
||||
for flag in required_flags:
|
||||
if not self.configuration.safe_get(flag):
|
||||
raise exception.InvalidInput(reason=_('%s is not set') % flag)
|
||||
|
||||
# Ensure that either password or keyfile were set
|
||||
if not (self.configuration.nas_password or
|
||||
self.configuration.nas_private_key):
|
||||
raise exception.InvalidInput(
|
||||
reason=_('Password or SSH private key is required for '
|
||||
'authentication: set either nas_password or '
|
||||
'nas_private_key option'))
|
||||
|
||||
# Ensure whether ibmnas platform type is set to appropriate value
|
||||
if self.configuration.ibmnas_platform_type not in valid_platforms:
|
||||
raise exception.InvalidInput(
|
||||
reason = (_("Unsupported ibmnas_platform_type: %(given)s."
|
||||
" Supported platforms: %(valid)s")
|
||||
% {'given': self.configuration.ibmnas_platform_type,
|
||||
'valid': (', '.join(valid_platforms))}))
|
||||
|
||||
def _get_provider_location(self, volume_id):
|
||||
"""Returns provider location for given volume."""
|
||||
LOG.debug("Enter _get_provider_location: volume_id %s", volume_id)
|
||||
volume = self.db.volume_get(self._context, volume_id)
|
||||
LOG.debug("Exit _get_provider_location")
|
||||
return volume['provider_location']
|
||||
|
||||
def _get_export_path(self, volume_id):
|
||||
"""Returns NFS export path for the given volume."""
|
||||
LOG.debug("Enter _get_export_path: volume_id %s", volume_id)
|
||||
return self._get_provider_location(volume_id).split(':')[1]
|
||||
|
||||
def _update_volume_stats(self):
|
||||
"""Retrieve stats info from volume group."""
|
||||
|
||||
LOG.debug("Enter _update_volume_stats")
|
||||
data = {}
|
||||
backend_name = self.configuration.safe_get('volume_backend_name')
|
||||
data['volume_backend_name'] = backend_name or 'IBMNAS_NFS'
|
||||
data['vendor_name'] = 'IBM'
|
||||
data['driver_version'] = self.get_version()
|
||||
data['storage_protocol'] = self.driver_volume_type
|
||||
|
||||
self._ensure_shares_mounted()
|
||||
|
||||
global_capacity = 0
|
||||
global_free = 0
|
||||
for share in self._mounted_shares:
|
||||
capacity, free, _used = self._get_capacity_info(share)
|
||||
global_capacity += capacity
|
||||
global_free += free
|
||||
|
||||
data['total_capacity_gb'] = global_capacity / float(units.Gi)
|
||||
data['free_capacity_gb'] = global_free / float(units.Gi)
|
||||
data['reserved_percentage'] = 0
|
||||
data['QoS_support'] = False
|
||||
self._stats = data
|
||||
LOG.debug("Exit _update_volume_stats")
|
||||
|
||||
def _ssh_operation(self, ssh_cmd):
|
||||
try:
|
||||
self._run_ssh(ssh_cmd)
|
||||
except processutils.ProcessExecutionError as e:
|
||||
msg = (_('Failed in _ssh_operation while execution of ssh_cmd:'
|
||||
'%(cmd)s. Error: %(error)s') %
|
||||
{'cmd': ssh_cmd, 'error': six.text_type(e)})
|
||||
LOG.exception(msg)
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
|
||||
def _create_ibmnas_snap(self, src, dest, mount_path):
|
||||
"""Create volume clones and snapshots."""
|
||||
LOG.debug("Enter _create_ibmnas_snap: src %(src)s, dest %(dest)s",
|
||||
{'src': src, 'dest': dest})
|
||||
if self.configuration.ibmnas_platform_type == 'gpfs-nas':
|
||||
ssh_cmd = ['mmclone', 'snap', src, dest]
|
||||
self._ssh_operation(ssh_cmd)
|
||||
else:
|
||||
if mount_path is not None:
|
||||
tmp_file_path = dest + '.snap'
|
||||
ssh_cmd = ['mkclone', '-p', dest, '-s', src, '-t',
|
||||
tmp_file_path]
|
||||
try:
|
||||
self._ssh_operation(ssh_cmd)
|
||||
finally:
|
||||
# Now remove the tmp file
|
||||
tmp_file_local_path = os.path.join(mount_path, os.path.
|
||||
basename(tmp_file_path))
|
||||
self._execute('rm', '-f', tmp_file_local_path,
|
||||
run_as_root=True)
|
||||
else:
|
||||
ssh_cmd = ['mkclone', '-s', src, '-t', dest]
|
||||
self._ssh_operation(ssh_cmd)
|
||||
LOG.debug("Exit _create_ibmnas_snap")
|
||||
|
||||
def _create_ibmnas_copy(self, src, dest, snap):
|
||||
"""Create a cloned volume, parent & the clone both remain writable."""
|
||||
LOG.debug('Enter _create_ibmnas_copy: src %(src)s, dest %(dest)s, '
|
||||
'snap %(snap)s', {'src': src,
|
||||
'dest': dest,
|
||||
'snap': snap})
|
||||
if self.configuration.ibmnas_platform_type == 'gpfs-nas':
|
||||
ssh_cmd = ['mmclone', 'snap', src, snap]
|
||||
self._ssh_operation(ssh_cmd)
|
||||
ssh_cmd = ['mmclone', 'copy', snap, dest]
|
||||
self._ssh_operation(ssh_cmd)
|
||||
else:
|
||||
ssh_cmd = ['mkclone', '-p', snap, '-s', src, '-t', dest]
|
||||
self._ssh_operation(ssh_cmd)
|
||||
LOG.debug("Exit _create_ibmnas_copy")
|
||||
|
||||
def _resize_volume_file(self, path, new_size):
|
||||
"""Resize the image file on share to new size."""
|
||||
LOG.debug("Resizing file to %sG.", new_size)
|
||||
try:
|
||||
image_utils.resize_image(path, new_size, run_as_root=True)
|
||||
except processutils.ProcessExecutionError as e:
|
||||
msg = (_("Failed to resize volume "
|
||||
"%(volume_id)s, error: %(error)s") %
|
||||
{'volume_id': os.path.basename(path).split('-')[1],
|
||||
'error': six.text_type(e.stderr)})
|
||||
LOG.error(msg)
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
return True
|
||||
|
||||
def extend_volume(self, volume, new_size):
|
||||
"""Extend an existing volume to the new size."""
|
||||
LOG.debug("Extending volume %s", volume['name'])
|
||||
path = self.local_path(volume)
|
||||
self._resize_volume_file(path, new_size)
|
||||
|
||||
def _delete_snapfiles(self, fchild, mount_point):
|
||||
LOG.debug('Enter _delete_snapfiles: fchild %(fchild)s, '
|
||||
'mount_point %(mount_point)s',
|
||||
{'fchild': fchild,
|
||||
'mount_point': mount_point})
|
||||
if self.configuration.ibmnas_platform_type == 'gpfs-nas':
|
||||
ssh_cmd = ['mmclone', 'show', fchild]
|
||||
else:
|
||||
ssh_cmd = ['lsclone', fchild]
|
||||
try:
|
||||
(out, _err) = self._run_ssh(ssh_cmd, check_exit_code=False)
|
||||
except processutils.ProcessExecutionError as e:
|
||||
msg = (_("Failed in _delete_snapfiles. Error: %s") %
|
||||
six.text_type(e.stderr))
|
||||
LOG.error(msg)
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
fparent = None
|
||||
reInode = re.compile(
|
||||
r'.*\s+(?:yes|no)\s+\d+\s+(?P<inode>\d+)', re.M | re.S)
|
||||
match = reInode.match(out)
|
||||
if match:
|
||||
inode = match.group('inode')
|
||||
path = mount_point
|
||||
(out, _err) = self._execute('find', path, '-maxdepth', '1',
|
||||
'-inum', inode, run_as_root=True)
|
||||
if out:
|
||||
fparent = out.split('\n', 1)[0]
|
||||
fchild_local_path = os.path.join(mount_point, os.path.basename(fchild))
|
||||
self._execute(
|
||||
'rm', '-f', fchild_local_path, check_exit_code=False,
|
||||
run_as_root=True)
|
||||
|
||||
# There is no need to check for volume references on this snapshot
|
||||
# because 'rm -f' itself serves as a simple and implicit check. If the
|
||||
# parent is referenced by another volume, system doesn't allow deleting
|
||||
# it. 'rm -f' silently fails and the subsequent check on the path
|
||||
# indicates whether there are any volumes derived from that snapshot.
|
||||
# If there are such volumes, we quit recursion and let the other
|
||||
# volumes delete the snapshot later. If there are no references, rm
|
||||
# would succeed and the snapshot is deleted.
|
||||
if not os.path.exists(fchild) and fparent:
|
||||
fpbase = os.path.basename(fparent)
|
||||
if (fpbase.endswith('.ts') or fpbase.endswith('.snap')):
|
||||
fparent_remote_path = os.path.join(os.path.dirname(fchild),
|
||||
fpbase)
|
||||
self._delete_snapfiles(fparent_remote_path, mount_point)
|
||||
LOG.debug("Exit _delete_snapfiles")
|
||||
|
||||
def delete_volume(self, volume):
|
||||
"""Deletes a logical volume."""
|
||||
if not volume['provider_location']:
|
||||
LOG.warning(_LW('Volume %s does not have '
|
||||
'provider_location specified, '
|
||||
'skipping.'), volume['name'])
|
||||
return
|
||||
|
||||
export_path = self._get_export_path(volume['id'])
|
||||
volume_name = volume['name']
|
||||
volume_path = os.path.join(export_path, volume_name)
|
||||
mount_point = os.path.dirname(self.local_path(volume))
|
||||
|
||||
# Delete all dependent snapshots, the snapshot will get deleted
|
||||
# if the link count goes to zero, else rm will fail silently
|
||||
self._delete_snapfiles(volume_path, mount_point)
|
||||
|
||||
def create_snapshot(self, snapshot):
|
||||
"""Creates a volume snapshot."""
|
||||
export_path = self._get_export_path(snapshot['volume_id'])
|
||||
snapshot_path = os.path.join(export_path, snapshot['name'])
|
||||
volume_path = os.path.join(export_path, snapshot['volume_name'])
|
||||
nfs_share = self._get_provider_location(snapshot['volume_id'])
|
||||
mount_path = self._get_mount_point_for_share(nfs_share)
|
||||
self._create_ibmnas_snap(src=volume_path, dest=snapshot_path,
|
||||
mount_path=mount_path)
|
||||
|
||||
def delete_snapshot(self, snapshot):
|
||||
"""Deletes a volume snapshot."""
|
||||
# A snapshot file is deleted as a part of delete_volume when
|
||||
# all volumes derived from it are deleted.
|
||||
|
||||
# Rename the deleted snapshot to indicate it no longer exists in
|
||||
# cinder db. Attempt to delete the snapshot. If the snapshot has
|
||||
# clone children, the delete will fail silently. When volumes that
|
||||
# are clone children are deleted in the future, the remaining ts
|
||||
# snapshots will also be deleted.
|
||||
nfs_share = self._get_provider_location(snapshot['volume_id'])
|
||||
mount_path = self._get_mount_point_for_share(nfs_share)
|
||||
snapshot_path = os.path.join(mount_path, snapshot['name'])
|
||||
snapshot_ts_path = '%s.ts' % snapshot_path
|
||||
self._execute('mv', '-f', snapshot_path, snapshot_ts_path,
|
||||
check_exit_code=True, run_as_root=True)
|
||||
self._execute('rm', '-f', snapshot_ts_path,
|
||||
check_exit_code=False, run_as_root=True)
|
||||
|
||||
def create_volume_from_snapshot(self, volume, snapshot):
|
||||
"""Creates a volume from an existing volume snapshot.
|
||||
|
||||
Extends the volume if the volume size is more than the snapshot size.
|
||||
"""
|
||||
export_path = self._get_export_path(snapshot['volume_id'])
|
||||
snapshot_path = os.path.join(export_path, snapshot.name)
|
||||
volume_path = os.path.join(export_path, volume['name'])
|
||||
|
||||
if self.configuration.ibmnas_platform_type == 'gpfs-nas':
|
||||
ssh_cmd = ['mmclone', 'copy', snapshot_path, volume_path]
|
||||
self._ssh_operation(ssh_cmd)
|
||||
else:
|
||||
self._create_ibmnas_snap(snapshot_path, volume_path, None)
|
||||
|
||||
volume['provider_location'] = self._find_share(volume['size'])
|
||||
volume_path = self.local_path(volume)
|
||||
self._set_rw_permissions_for_owner(volume_path)
|
||||
|
||||
# Extend the volume if required
|
||||
self._resize_volume_file(volume_path, volume['size'])
|
||||
return {'provider_location': volume['provider_location']}
|
||||
|
||||
def create_cloned_volume(self, volume, src_vref):
|
||||
"""Creates a clone of the specified volume.
|
||||
|
||||
Extends the volume if the new volume size is more than
|
||||
the source volume size.
|
||||
"""
|
||||
export_path = self._get_export_path(src_vref['id'])
|
||||
src_vol_path = os.path.join(export_path, src_vref['name'])
|
||||
dest_vol_path = os.path.join(export_path, volume['name'])
|
||||
snap_file_name = volume['name']
|
||||
snap_file_name = snap_file_name + '.snap'
|
||||
snap_file_path = os.path.join(export_path, snap_file_name)
|
||||
self._create_ibmnas_copy(src_vol_path, dest_vol_path, snap_file_path)
|
||||
|
||||
volume['provider_location'] = self._find_share(volume['size'])
|
||||
volume_path = self.local_path(volume)
|
||||
self._set_rw_permissions_for_owner(volume_path)
|
||||
|
||||
# Extend the volume if required
|
||||
self._resize_volume_file(volume_path, volume['size'])
|
||||
|
||||
return {'provider_location': volume['provider_location']}
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
upgrade:
|
||||
- Users of the ibmnas driver should switch to using the IBM GPFS driver to enable Cinder access to IBM NAS resources. For details configuring the IBM GPFS driver, see the GPFS config reference. - http://docs.openstack.org/liberty/config-reference/content/GPFS-driver.html
|
||||
other:
|
||||
- Due to the ibmnas (SONAS) driver being rendered redundant by the addition of NFS capabilities to the IBM GPFS driver, the ibmnas driver is being removed in the Mitaka release.
|
Loading…
Reference in New Issue
Block a user