Add standard QoS spec support to cDOT drivers
This commit adds support for standard cinder QoS specs to NetApp cDOT drivers, alongside our pre-existing support for externally provisioned QoS policy groups via qualified extra specs. Implements-blueprint: add-qos-spec-support Change-Id: I4bd123020d00866a346ad02919ac1d82f7236134
This commit is contained in:
parent
a6f23bc01d
commit
9f2185f4ca
|
@ -136,7 +136,6 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
|||
kwargs['configuration'] = create_configuration()
|
||||
self._driver = netapp_nfs_cmode.NetAppCmodeNfsDriver(**kwargs)
|
||||
self._driver.zapi_client = mock.Mock()
|
||||
|
||||
config = self._driver.configuration
|
||||
config.netapp_vserver = FAKE_VSERVER
|
||||
|
||||
|
@ -145,10 +144,10 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
|||
mox = self.mox
|
||||
drv = self._driver
|
||||
|
||||
mox.StubOutWithMock(drv, '_clone_volume')
|
||||
drv._clone_volume(mox_lib.IgnoreArg(),
|
||||
mox_lib.IgnoreArg(),
|
||||
mox_lib.IgnoreArg())
|
||||
mox.StubOutWithMock(drv, '_clone_backing_file_for_volume')
|
||||
drv._clone_backing_file_for_volume(mox_lib.IgnoreArg(),
|
||||
mox_lib.IgnoreArg(),
|
||||
mox_lib.IgnoreArg())
|
||||
mox.ReplayAll()
|
||||
|
||||
drv.create_snapshot(FakeSnapshot())
|
||||
|
@ -165,14 +164,14 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
|||
snapshot = FakeSnapshot(1)
|
||||
|
||||
expected_result = {'provider_location': location}
|
||||
mox.StubOutWithMock(drv, '_clone_volume')
|
||||
mox.StubOutWithMock(drv, '_clone_backing_file_for_volume')
|
||||
mox.StubOutWithMock(drv, '_get_volume_location')
|
||||
mox.StubOutWithMock(drv, 'local_path')
|
||||
mox.StubOutWithMock(drv, '_discover_file_till_timeout')
|
||||
mox.StubOutWithMock(drv, '_set_rw_permissions')
|
||||
drv._clone_volume(mox_lib.IgnoreArg(),
|
||||
mox_lib.IgnoreArg(),
|
||||
mox_lib.IgnoreArg())
|
||||
drv._clone_backing_file_for_volume(mox_lib.IgnoreArg(),
|
||||
mox_lib.IgnoreArg(),
|
||||
mox_lib.IgnoreArg())
|
||||
drv._get_volume_location(mox_lib.IgnoreArg()).AndReturn(location)
|
||||
drv.local_path(mox_lib.IgnoreArg()).AndReturn('/mnt')
|
||||
drv._discover_file_till_timeout(mox_lib.IgnoreArg()).AndReturn(True)
|
||||
|
@ -180,6 +179,9 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
|||
|
||||
mox.ReplayAll()
|
||||
|
||||
self.mock_object(drv, '_do_qos_for_volume')
|
||||
self.mock_object(utils, 'get_volume_extra_specs')
|
||||
|
||||
loc = drv.create_volume_from_snapshot(volume, snapshot)
|
||||
|
||||
self.assertEqual(loc, expected_result)
|
||||
|
@ -300,7 +302,7 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
|||
response_el = etree.XML(res)
|
||||
return api.NaElement(response_el).get_children()
|
||||
|
||||
def test_clone_volume(self):
|
||||
def test_clone_backing_file_for_volume(self):
|
||||
drv = self._driver
|
||||
mox = self._prepare_clone_mock('pass')
|
||||
|
||||
|
@ -311,7 +313,8 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
|||
volume_id = volume_name + six.text_type(hash(volume_name))
|
||||
share = 'ip:/share'
|
||||
|
||||
drv._clone_volume(volume_name, clone_name, volume_id, share)
|
||||
drv._clone_backing_file_for_volume(volume_name, clone_name, volume_id,
|
||||
share)
|
||||
|
||||
mox.VerifyAll()
|
||||
|
||||
|
@ -425,11 +428,11 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
|||
mox = self.mox
|
||||
files = [('img-cache-1', 230), ('img-cache-2', 380)]
|
||||
mox.StubOutWithMock(drv, '_get_mount_point_for_share')
|
||||
mox.StubOutWithMock(drv, '_delete_file')
|
||||
mox.StubOutWithMock(drv, '_delete_file_at_path')
|
||||
|
||||
drv._get_mount_point_for_share(mox_lib.IgnoreArg()).AndReturn('/mnt')
|
||||
drv._delete_file('/mnt/img-cache-2').AndReturn(True)
|
||||
drv._delete_file('/mnt/img-cache-1').AndReturn(True)
|
||||
drv._delete_file_at_path('/mnt/img-cache-2').AndReturn(True)
|
||||
drv._delete_file_at_path('/mnt/img-cache-1').AndReturn(True)
|
||||
mox.ReplayAll()
|
||||
drv._delete_files_till_bytes_free(files, 'share', bytes_to_free=1024)
|
||||
mox.VerifyAll()
|
||||
|
@ -481,11 +484,13 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
|||
drv = self._driver
|
||||
mox = self.mox
|
||||
volume = {'name': 'vol', 'size': '20'}
|
||||
mox.StubOutWithMock(utils, 'get_volume_extra_specs')
|
||||
mox.StubOutWithMock(drv, '_find_image_in_cache')
|
||||
mox.StubOutWithMock(drv, '_do_clone_rel_img_cache')
|
||||
mox.StubOutWithMock(drv, '_post_clone_image')
|
||||
mox.StubOutWithMock(drv, '_is_share_vol_compatible')
|
||||
|
||||
utils.get_volume_extra_specs(mox_lib.IgnoreArg())
|
||||
drv._find_image_in_cache(mox_lib.IgnoreArg()).AndReturn(
|
||||
[('share', 'file_name')])
|
||||
drv._is_share_vol_compatible(mox_lib.IgnoreArg(),
|
||||
|
@ -511,10 +516,12 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
|||
drv = self._driver
|
||||
mox = self.mox
|
||||
volume = {'name': 'vol', 'size': '20'}
|
||||
mox.StubOutWithMock(utils, 'get_volume_extra_specs')
|
||||
mox.StubOutWithMock(drv, '_find_image_in_cache')
|
||||
mox.StubOutWithMock(drv, '_is_cloneable_share')
|
||||
mox.StubOutWithMock(drv, '_is_share_vol_compatible')
|
||||
|
||||
utils.get_volume_extra_specs(mox_lib.IgnoreArg())
|
||||
drv._find_image_in_cache(mox_lib.IgnoreArg()).AndReturn([])
|
||||
drv._is_cloneable_share(
|
||||
mox_lib.IgnoreArg()).AndReturn('127.0.0.1:/share')
|
||||
|
@ -538,16 +545,18 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
|||
drv = self._driver
|
||||
mox = self.mox
|
||||
volume = {'name': 'vol', 'size': '20'}
|
||||
mox.StubOutWithMock(utils, 'get_volume_extra_specs')
|
||||
mox.StubOutWithMock(drv, '_find_image_in_cache')
|
||||
mox.StubOutWithMock(drv, '_is_cloneable_share')
|
||||
mox.StubOutWithMock(drv, '_get_mount_point_for_share')
|
||||
mox.StubOutWithMock(image_utils, 'qemu_img_info')
|
||||
mox.StubOutWithMock(drv, '_clone_volume')
|
||||
mox.StubOutWithMock(drv, '_clone_backing_file_for_volume')
|
||||
mox.StubOutWithMock(drv, '_discover_file_till_timeout')
|
||||
mox.StubOutWithMock(drv, '_set_rw_permissions')
|
||||
mox.StubOutWithMock(drv, '_resize_image_file')
|
||||
mox.StubOutWithMock(drv, '_is_share_vol_compatible')
|
||||
|
||||
utils.get_volume_extra_specs(mox_lib.IgnoreArg())
|
||||
drv._find_image_in_cache(mox_lib.IgnoreArg()).AndReturn([])
|
||||
drv._is_cloneable_share(
|
||||
mox_lib.IgnoreArg()).AndReturn('127.0.0.1:/share')
|
||||
|
@ -556,7 +565,7 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
|||
drv._get_mount_point_for_share(mox_lib.IgnoreArg()).AndReturn('/mnt')
|
||||
image_utils.qemu_img_info('/mnt/img-id', run_as_root=True).\
|
||||
AndReturn(self.get_img_info('raw'))
|
||||
drv._clone_volume(
|
||||
drv._clone_backing_file_for_volume(
|
||||
'img-id', 'vol', share='127.0.0.1:/share', volume_id=None)
|
||||
drv._get_mount_point_for_share(mox_lib.IgnoreArg()).AndReturn('/mnt')
|
||||
drv._discover_file_till_timeout(mox_lib.IgnoreArg()).AndReturn(True)
|
||||
|
@ -576,11 +585,12 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
|||
drv = self._driver
|
||||
mox = self.mox
|
||||
volume = {'name': 'vol', 'size': '20'}
|
||||
mox.StubOutWithMock(utils, 'get_volume_extra_specs')
|
||||
mox.StubOutWithMock(drv, '_find_image_in_cache')
|
||||
mox.StubOutWithMock(drv, '_is_cloneable_share')
|
||||
mox.StubOutWithMock(drv, '_get_mount_point_for_share')
|
||||
mox.StubOutWithMock(image_utils, 'qemu_img_info')
|
||||
mox.StubOutWithMock(drv, '_clone_volume')
|
||||
mox.StubOutWithMock(drv, '_clone_backing_file_for_volume')
|
||||
mox.StubOutWithMock(drv, '_discover_file_till_timeout')
|
||||
mox.StubOutWithMock(drv, '_set_rw_permissions')
|
||||
mox.StubOutWithMock(drv, '_resize_image_file')
|
||||
|
@ -588,6 +598,7 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
|||
mox.StubOutWithMock(drv, '_register_image_in_cache')
|
||||
mox.StubOutWithMock(drv, '_is_share_vol_compatible')
|
||||
|
||||
utils.get_volume_extra_specs(mox_lib.IgnoreArg())
|
||||
drv._find_image_in_cache(mox_lib.IgnoreArg()).AndReturn([])
|
||||
drv._is_cloneable_share('nfs://127.0.0.1/share/img-id').AndReturn(
|
||||
'127.0.0.1:/share')
|
||||
|
@ -620,19 +631,20 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
|||
drv = self._driver
|
||||
mox = self.mox
|
||||
volume = {'name': 'vol', 'size': '20'}
|
||||
mox.StubOutWithMock(utils, 'get_volume_extra_specs')
|
||||
mox.StubOutWithMock(drv, '_find_image_in_cache')
|
||||
mox.StubOutWithMock(drv, '_is_cloneable_share')
|
||||
mox.StubOutWithMock(drv, '_get_mount_point_for_share')
|
||||
mox.StubOutWithMock(image_utils, 'qemu_img_info')
|
||||
mox.StubOutWithMock(drv, '_clone_volume')
|
||||
mox.StubOutWithMock(drv, '_clone_backing_file_for_volume')
|
||||
mox.StubOutWithMock(drv, '_discover_file_till_timeout')
|
||||
mox.StubOutWithMock(image_utils, 'convert_image')
|
||||
mox.StubOutWithMock(drv, '_register_image_in_cache')
|
||||
mox.StubOutWithMock(drv, '_is_share_vol_compatible')
|
||||
mox.StubOutWithMock(drv, '_do_qos_for_volume')
|
||||
mox.StubOutWithMock(drv, 'local_path')
|
||||
mox.StubOutWithMock(os.path, 'exists')
|
||||
mox.StubOutWithMock(drv, '_delete_file')
|
||||
|
||||
utils.get_volume_extra_specs(mox_lib.IgnoreArg())
|
||||
drv._find_image_in_cache(mox_lib.IgnoreArg()).AndReturn([])
|
||||
drv._is_cloneable_share('nfs://127.0.0.1/share/img-id').AndReturn(
|
||||
'127.0.0.1:/share')
|
||||
|
@ -648,11 +660,9 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
|||
AndReturn(self.get_img_info('raw'))
|
||||
drv._register_image_in_cache(mox_lib.IgnoreArg(),
|
||||
mox_lib.IgnoreArg())
|
||||
drv._do_qos_for_volume(mox_lib.IgnoreArg(), mox_lib.IgnoreArg())
|
||||
drv.local_path(mox_lib.IgnoreArg()).AndReturn('/mnt/vol')
|
||||
drv._discover_file_till_timeout(mox_lib.IgnoreArg()).AndReturn(False)
|
||||
drv.local_path(mox_lib.IgnoreArg()).AndReturn('/mnt/vol')
|
||||
os.path.exists('/mnt/vol').AndReturn(True)
|
||||
drv._delete_file('/mnt/vol')
|
||||
|
||||
mox.ReplayAll()
|
||||
vol_dict, result = drv.clone_image(
|
||||
|
@ -670,21 +680,22 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
|||
drv = self._driver
|
||||
mox = self.mox
|
||||
volume = {'name': 'vol', 'size': '20'}
|
||||
mox.StubOutWithMock(utils, 'get_volume_extra_specs')
|
||||
mox.StubOutWithMock(drv, '_find_image_in_cache')
|
||||
mox.StubOutWithMock(drv, '_is_cloneable_share')
|
||||
mox.StubOutWithMock(drv, '_get_mount_point_for_share')
|
||||
mox.StubOutWithMock(image_utils, 'qemu_img_info')
|
||||
mox.StubOutWithMock(drv, '_clone_volume')
|
||||
mox.StubOutWithMock(drv, '_clone_backing_file_for_volume')
|
||||
mox.StubOutWithMock(drv, '_discover_file_till_timeout')
|
||||
mox.StubOutWithMock(drv, '_set_rw_permissions')
|
||||
mox.StubOutWithMock(drv, '_resize_image_file')
|
||||
mox.StubOutWithMock(image_utils, 'convert_image')
|
||||
mox.StubOutWithMock(drv, '_do_qos_for_volume')
|
||||
mox.StubOutWithMock(drv, '_register_image_in_cache')
|
||||
mox.StubOutWithMock(drv, '_is_share_vol_compatible')
|
||||
mox.StubOutWithMock(drv, 'local_path')
|
||||
mox.StubOutWithMock(os.path, 'exists')
|
||||
mox.StubOutWithMock(drv, '_delete_file')
|
||||
|
||||
utils.get_volume_extra_specs(mox_lib.IgnoreArg())
|
||||
drv._find_image_in_cache(mox_lib.IgnoreArg()).AndReturn([])
|
||||
drv._is_cloneable_share('nfs://127.0.0.1/share/img-id').AndReturn(
|
||||
'127.0.0.1:/share')
|
||||
|
@ -700,15 +711,13 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
|||
AndReturn(self.get_img_info('raw'))
|
||||
drv._register_image_in_cache(mox_lib.IgnoreArg(),
|
||||
mox_lib.IgnoreArg())
|
||||
drv._do_qos_for_volume(mox_lib.IgnoreArg(), mox_lib.IgnoreArg())
|
||||
drv.local_path(mox_lib.IgnoreArg()).AndReturn('/mnt/vol')
|
||||
drv._discover_file_till_timeout(mox_lib.IgnoreArg()).AndReturn(True)
|
||||
drv._set_rw_permissions('/mnt/vol')
|
||||
drv._resize_image_file(
|
||||
mox_lib.IgnoreArg(),
|
||||
mox_lib.IgnoreArg()).AndRaise(exception.InvalidResults())
|
||||
drv.local_path(mox_lib.IgnoreArg()).AndReturn('/mnt/vol')
|
||||
os.path.exists('/mnt/vol').AndReturn(True)
|
||||
drv._delete_file('/mnt/vol')
|
||||
|
||||
mox.ReplayAll()
|
||||
vol_dict, result = drv.clone_image(
|
||||
|
@ -942,24 +951,6 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
|||
self.assertEqual('446', na_server.get_port())
|
||||
self.assertEqual('https', na_server.get_transport_type())
|
||||
|
||||
@mock.patch.object(utils, 'get_volume_extra_specs')
|
||||
def test_check_volume_type_qos(self, get_specs):
|
||||
get_specs.return_value = {'netapp:qos_policy_group': 'qos'}
|
||||
self._driver._get_vserver_and_exp_vol = mock.Mock(
|
||||
return_value=('vs', 'vol'))
|
||||
self._driver.zapi_client.file_assign_qos = mock.Mock(
|
||||
side_effect=api.NaApiError)
|
||||
self._driver._is_share_vol_type_match = mock.Mock(return_value=True)
|
||||
self.assertRaises(exception.NetAppDriverException,
|
||||
self._driver._check_volume_type, 'vol',
|
||||
'share', 'file')
|
||||
get_specs.assert_called_once_with('vol')
|
||||
self.assertEqual(1,
|
||||
self._driver.zapi_client.file_assign_qos.call_count)
|
||||
self.assertEqual(1, self._driver._get_vserver_and_exp_vol.call_count)
|
||||
self._driver._is_share_vol_type_match.assert_called_once_with(
|
||||
'vol', 'share')
|
||||
|
||||
@mock.patch.object(utils, 'resolve_hostname', return_value='10.12.142.11')
|
||||
def test_convert_vol_ref_share_name_to_share_ip(self, mock_hostname):
|
||||
drv = self._driver
|
||||
|
@ -1114,11 +1105,15 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
|||
return_value=(self.TEST_NFS_EXPORT1, self.TEST_MNT_POINT,
|
||||
test_file))
|
||||
shutil.move = mock.Mock()
|
||||
mock_get_specs = self.mock_object(utils, 'get_volume_extra_specs')
|
||||
mock_get_specs.return_value = {}
|
||||
self.mock_object(drv, '_do_qos_for_volume')
|
||||
|
||||
location = drv.manage_existing(volume, vol_ref)
|
||||
|
||||
self.assertEqual(self.TEST_NFS_EXPORT1, location['provider_location'])
|
||||
drv._check_volume_type.assert_called_once_with(
|
||||
volume, self.TEST_NFS_EXPORT1, test_file)
|
||||
volume, self.TEST_NFS_EXPORT1, test_file, {})
|
||||
|
||||
@mock.patch.object(cinder_utils, 'get_file_size', return_value=1074253824)
|
||||
def test_manage_existing_move_fails(self, get_file_size):
|
||||
|
@ -1130,7 +1125,7 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
|||
volume['id'] = 'volume-new-managed-123'
|
||||
vol_path = "%s/%s" % (self.TEST_NFS_EXPORT1, test_file)
|
||||
vol_ref = {'source-name': vol_path}
|
||||
drv._check_volume_type = mock.Mock()
|
||||
mock_check_volume_type = drv._check_volume_type = mock.Mock()
|
||||
drv._ensure_shares_mounted = mock.Mock()
|
||||
drv._get_mount_point_for_share = mock.Mock(
|
||||
return_value=self.TEST_MNT_POINT)
|
||||
|
@ -1138,18 +1133,26 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
|||
return_value=(self.TEST_NFS_EXPORT1, self.TEST_MNT_POINT,
|
||||
test_file))
|
||||
drv._execute = mock.Mock(side_effect=OSError)
|
||||
mock_get_specs = self.mock_object(utils, 'get_volume_extra_specs')
|
||||
mock_get_specs.return_value = {}
|
||||
self.mock_object(drv, '_do_qos_for_volume')
|
||||
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
drv.manage_existing, volume, vol_ref)
|
||||
drv._check_volume_type.assert_called_once_with(
|
||||
volume, self.TEST_NFS_EXPORT1, test_file)
|
||||
|
||||
mock_check_volume_type.assert_called_once_with(
|
||||
volume, self.TEST_NFS_EXPORT1, test_file, {})
|
||||
|
||||
@mock.patch.object(nfs_base, 'LOG')
|
||||
def test_unmanage(self, mock_log):
|
||||
drv = self._driver
|
||||
self.mock_object(utils, 'get_valid_qos_policy_group_info')
|
||||
volume = FakeVolume()
|
||||
volume['id'] = '123'
|
||||
volume['provider_location'] = '/share'
|
||||
|
||||
drv.unmanage(volume)
|
||||
|
||||
self.assertEqual(1, mock_log.info.call_count)
|
||||
|
||||
|
||||
|
@ -1169,64 +1172,36 @@ class NetAppCmodeNfsDriverOnlyTestCase(test.TestCase):
|
|||
self._driver.ssc_enabled = True
|
||||
self._driver.configuration.netapp_copyoffload_tool_path = 'cof_path'
|
||||
self._driver.zapi_client = mock.Mock()
|
||||
|
||||
@mock.patch.object(utils, 'get_volume_extra_specs')
|
||||
@mock.patch.object(utils, 'LOG', mock.Mock())
|
||||
def test_create_volume(self, mock_volume_extra_specs):
|
||||
drv = self._driver
|
||||
drv.ssc_enabled = False
|
||||
extra_specs = {}
|
||||
mock_volume_extra_specs.return_value = extra_specs
|
||||
fake_share = 'localhost:myshare'
|
||||
host = 'hostname@backend#' + fake_share
|
||||
with mock.patch.object(drv, '_ensure_shares_mounted'):
|
||||
with mock.patch.object(drv, '_do_create_volume'):
|
||||
volume_info = self._driver.create_volume(FakeVolume(host, 1))
|
||||
self.assertEqual(volume_info.get('provider_location'),
|
||||
fake_share)
|
||||
self.assertEqual(0, utils.LOG.warning.call_count)
|
||||
self._fake_empty_qos_policy_group_info = {
|
||||
'legacy': None,
|
||||
'spec': None,
|
||||
}
|
||||
self._fake_legacy_qos_policy_group_info = {
|
||||
'legacy': {
|
||||
'policy_name': 'qos_policy_1'
|
||||
},
|
||||
'spec': None,
|
||||
}
|
||||
|
||||
@mock.patch.object(utils, 'LOG', mock.Mock())
|
||||
def test_create_volume_obsolete_extra_spec(self):
|
||||
def test_create_volume(self):
|
||||
drv = self._driver
|
||||
drv.ssc_enabled = False
|
||||
extra_specs = {'netapp:raid_type': 'raid4'}
|
||||
mock_volume_extra_specs = mock.Mock()
|
||||
self.mock_object(utils,
|
||||
'get_volume_extra_specs',
|
||||
mock_volume_extra_specs)
|
||||
mock_volume_extra_specs.return_value = extra_specs
|
||||
fake_extra_specs = {}
|
||||
fake_share = 'localhost:myshare'
|
||||
host = 'hostname@backend#' + fake_share
|
||||
with mock.patch.object(drv, '_ensure_shares_mounted'):
|
||||
with mock.patch.object(drv, '_do_create_volume'):
|
||||
self._driver.create_volume(FakeVolume(host, 1))
|
||||
warn_msg = ('Extra spec %(old)s is obsolete. Use %(new)s '
|
||||
'instead.')
|
||||
utils.LOG.warning.assert_called_once_with(
|
||||
warn_msg, {'new': 'netapp_raid_type',
|
||||
'old': 'netapp:raid_type'})
|
||||
mock_get_specs = self.mock_object(utils, 'get_volume_extra_specs')
|
||||
mock_get_specs.return_value = fake_extra_specs
|
||||
self.mock_object(drv, '_ensure_shares_mounted')
|
||||
self.mock_object(drv, '_do_create_volume')
|
||||
mock_get_qos_info =\
|
||||
self.mock_object(utils, 'get_valid_qos_policy_group_info')
|
||||
mock_get_qos_info.return_value = self._fake_empty_qos_policy_group_info
|
||||
|
||||
@mock.patch.object(utils, 'LOG', mock.Mock())
|
||||
def test_create_volume_deprecated_extra_spec(self):
|
||||
drv = self._driver
|
||||
drv.ssc_enabled = False
|
||||
extra_specs = {'netapp_thick_provisioned': 'true'}
|
||||
fake_share = 'localhost:myshare'
|
||||
host = 'hostname@backend#' + fake_share
|
||||
mock_volume_extra_specs = mock.Mock()
|
||||
self.mock_object(utils,
|
||||
'get_volume_extra_specs',
|
||||
mock_volume_extra_specs)
|
||||
mock_volume_extra_specs.return_value = extra_specs
|
||||
with mock.patch.object(drv, '_ensure_shares_mounted'):
|
||||
with mock.patch.object(drv, '_do_create_volume'):
|
||||
self._driver.create_volume(FakeVolume(host, 1))
|
||||
warn_msg = ('Extra spec %(old)s is deprecated. Use %(new)s '
|
||||
'instead.')
|
||||
utils.LOG.warning.assert_called_once_with(
|
||||
warn_msg, {'new': 'netapp_thin_provisioned',
|
||||
'old': 'netapp_thick_provisioned'})
|
||||
volume_info = self._driver.create_volume(FakeVolume(host, 1))
|
||||
|
||||
self.assertEqual(fake_share, volume_info.get('provider_location'))
|
||||
self.assertEqual(0, utils.LOG.warning.call_count)
|
||||
|
||||
def test_create_volume_no_pool_specified(self):
|
||||
drv = self._driver
|
||||
|
@ -1236,28 +1211,29 @@ class NetAppCmodeNfsDriverOnlyTestCase(test.TestCase):
|
|||
self.assertRaises(exception.InvalidHost,
|
||||
self._driver.create_volume, FakeVolume(host, 1))
|
||||
|
||||
@mock.patch.object(utils, 'get_volume_extra_specs')
|
||||
def test_create_volume_with_qos_policy(self, mock_volume_extra_specs):
|
||||
def test_create_volume_with_legacy_qos_policy(self):
|
||||
drv = self._driver
|
||||
drv.ssc_enabled = False
|
||||
extra_specs = {'netapp:qos_policy_group': 'qos_policy_1'}
|
||||
fake_extra_specs = {'netapp:qos_policy_group': 'qos_policy_1'}
|
||||
fake_share = 'localhost:myshare'
|
||||
host = 'hostname@backend#' + fake_share
|
||||
fake_volume = FakeVolume(host, 1)
|
||||
fake_qos_policy = 'qos_policy_1'
|
||||
mock_volume_extra_specs.return_value = extra_specs
|
||||
mock_get_specs = self.mock_object(utils, 'get_volume_extra_specs')
|
||||
mock_get_specs.return_value = fake_extra_specs
|
||||
mock_get_qos_info =\
|
||||
self.mock_object(utils, 'get_valid_qos_policy_group_info')
|
||||
mock_get_qos_info.return_value =\
|
||||
self._fake_legacy_qos_policy_group_info
|
||||
self.mock_object(drv, '_ensure_shares_mounted')
|
||||
self.mock_object(drv, '_do_create_volume')
|
||||
mock_set_qos = self.mock_object(drv, '_set_qos_policy_group_on_volume')
|
||||
|
||||
with mock.patch.object(drv, '_ensure_shares_mounted'):
|
||||
with mock.patch.object(drv, '_do_create_volume'):
|
||||
with mock.patch.object(drv,
|
||||
'_set_qos_policy_group_on_volume'
|
||||
) as mock_set_qos:
|
||||
volume_info = self._driver.create_volume(fake_volume)
|
||||
self.assertEqual(volume_info.get('provider_location'),
|
||||
'localhost:myshare')
|
||||
mock_set_qos.assert_called_once_with(fake_volume,
|
||||
fake_share,
|
||||
fake_qos_policy)
|
||||
volume_info = self._driver.create_volume(fake_volume)
|
||||
|
||||
self.assertEqual('localhost:myshare',
|
||||
volume_info.get('provider_location'))
|
||||
mock_set_qos.assert_called_once_with(
|
||||
fake_volume, self._fake_legacy_qos_policy_group_info)
|
||||
|
||||
def test_copy_img_to_vol_copyoffload_success(self):
|
||||
drv = self._driver
|
||||
|
@ -1408,7 +1384,7 @@ class NetAppCmodeNfsDriverOnlyTestCase(test.TestCase):
|
|||
mock_qemu_img_info.return_value = img_inf
|
||||
drv._check_share_can_hold_size = mock.Mock()
|
||||
drv._move_nfs_file = mock.Mock(return_value=True)
|
||||
drv._delete_file = mock.Mock()
|
||||
drv._delete_file_at_path = mock.Mock()
|
||||
drv._clone_file_dst_exists = mock.Mock()
|
||||
drv._post_clone_image = mock.Mock()
|
||||
|
||||
|
@ -1450,7 +1426,7 @@ class NetAppCmodeNfsDriverOnlyTestCase(test.TestCase):
|
|||
drv._check_share_can_hold_size = mock.Mock()
|
||||
|
||||
drv._move_nfs_file = mock.Mock(return_value=True)
|
||||
drv._delete_file = mock.Mock()
|
||||
drv._delete_file_at_path = mock.Mock()
|
||||
drv._clone_file_dst_exists = mock.Mock()
|
||||
drv._post_clone_image = mock.Mock()
|
||||
|
||||
|
@ -1460,7 +1436,7 @@ class NetAppCmodeNfsDriverOnlyTestCase(test.TestCase):
|
|||
drv._check_share_can_hold_size.assert_called_with('share', 1)
|
||||
assert mock_cvrt_image.call_count == 1
|
||||
assert drv._execute.call_count == 1
|
||||
assert drv._delete_file.call_count == 2
|
||||
assert drv._delete_file_at_path.call_count == 2
|
||||
drv._clone_file_dst_exists.call_count == 1
|
||||
drv._post_clone_image.assert_called_with(volume)
|
||||
|
||||
|
@ -1543,7 +1519,7 @@ class NetApp7modeNfsDriverTestCase(NetAppCmodeNfsDriverTestCase):
|
|||
mox_lib.IgnoreArg()).AndReturn(('127.0.0.1', '/nfs'))
|
||||
return mox
|
||||
|
||||
def test_clone_volume_clear(self):
|
||||
def test_clone_backing_file_for_volume_clear(self):
|
||||
drv = self._driver
|
||||
mox = self._prepare_clone_mock('fail')
|
||||
drv.zapi_client = mox.CreateMockAnything()
|
||||
|
@ -1557,7 +1533,8 @@ class NetApp7modeNfsDriverTestCase(NetAppCmodeNfsDriverTestCase):
|
|||
clone_name = 'clone_name'
|
||||
volume_id = volume_name + six.text_type(hash(volume_name))
|
||||
try:
|
||||
drv._clone_volume(volume_name, clone_name, volume_id)
|
||||
drv._clone_backing_file_for_volume(volume_name, clone_name,
|
||||
volume_id)
|
||||
except Exception as e:
|
||||
if isinstance(e, api.NaApiError):
|
||||
pass
|
||||
|
@ -1570,21 +1547,13 @@ class NetApp7modeNfsDriverTestCase(NetAppCmodeNfsDriverTestCase):
|
|||
pool = self._driver.get_pool({'provider_location': 'fake-share'})
|
||||
self.assertEqual(pool, 'fake-share')
|
||||
|
||||
@mock.patch.object(utils, 'get_volume_extra_specs')
|
||||
def test_check_volume_type_qos(self, get_specs):
|
||||
get_specs.return_value = {'netapp:qos_policy_group': 'qos'}
|
||||
self.assertRaises(exception.ManageExistingVolumeTypeMismatch,
|
||||
self._driver._check_volume_type,
|
||||
'vol', 'share', 'file')
|
||||
get_specs.assert_called_once_with('vol')
|
||||
|
||||
def _set_config(self, configuration):
|
||||
super(NetApp7modeNfsDriverTestCase, self)._set_config(
|
||||
configuration)
|
||||
configuration.netapp_storage_family = 'ontap_7mode'
|
||||
return configuration
|
||||
|
||||
def test_clone_volume(self):
|
||||
def test_clone_backing_file_for_volume(self):
|
||||
drv = self._driver
|
||||
mox = self._prepare_clone_mock('pass')
|
||||
drv.zapi_client = mox.CreateMockAnything()
|
||||
|
@ -1599,6 +1568,7 @@ class NetApp7modeNfsDriverTestCase(NetAppCmodeNfsDriverTestCase):
|
|||
volume_id = volume_name + six.text_type(hash(volume_name))
|
||||
share = 'ip:/share'
|
||||
|
||||
drv._clone_volume(volume_name, clone_name, volume_id, share)
|
||||
drv._clone_backing_file_for_volume(volume_name, clone_name, volume_id,
|
||||
share)
|
||||
|
||||
mox.VerifyAll()
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Copyright (c) 2012 NetApp, Inc.
|
||||
# All Rights Reserved.
|
||||
# Copyright (c) 2012 NetApp, Inc. All rights reserved.
|
||||
# Copyright (c) 2015 Tom Barron. 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
|
||||
|
@ -360,6 +360,16 @@ class SscUtilsTestCase(test.TestCase):
|
|||
dedup=True, compression=False,
|
||||
raid='raid4', ha='cfo', disk='SAS')
|
||||
|
||||
test_vols = {vol1, vol2, vol3, vol4, vol5}
|
||||
|
||||
ssc_map = {
|
||||
'mirrored': {vol1},
|
||||
'dedup': {vol1, vol2, vol3},
|
||||
'compression': {vol3, vol4},
|
||||
'thin': {vol5, vol2},
|
||||
'all': test_vols
|
||||
}
|
||||
|
||||
def setUp(self):
|
||||
super(SscUtilsTestCase, self).setUp()
|
||||
self.stubs.Set(httplib, 'HTTPConnection',
|
||||
|
@ -504,18 +514,38 @@ class SscUtilsTestCase(test.TestCase):
|
|||
|
||||
def test_vols_for_optional_specs(self):
|
||||
"""Test ssc for optional specs."""
|
||||
test_vols =\
|
||||
set([self.vol1, self.vol2, self.vol3, self.vol4, self.vol5])
|
||||
ssc_map = {'mirrored': set([self.vol1]),
|
||||
'dedup': set([self.vol1, self.vol2, self.vol3]),
|
||||
'compression': set([self.vol3, self.vol4]),
|
||||
'thin': set([self.vol5, self.vol2]), 'all': test_vols}
|
||||
extra_specs =\
|
||||
{'netapp_dedup': 'true',
|
||||
'netapp:raid_type': 'raid4', 'netapp:disk_type': 'SSD'}
|
||||
res = ssc_cmode.get_volumes_for_specs(ssc_map, extra_specs)
|
||||
res = ssc_cmode.get_volumes_for_specs(self.ssc_map, extra_specs)
|
||||
self.assertEqual(len(res), 1)
|
||||
|
||||
def test_get_volumes_for_specs_none_specs(self):
|
||||
none_specs = None
|
||||
expected = self.ssc_map['all']
|
||||
|
||||
result = ssc_cmode.get_volumes_for_specs(self.ssc_map, none_specs)
|
||||
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_get_volumes_for_specs_empty_dict(self):
|
||||
empty_dict = {}
|
||||
expected = self.ssc_map['all']
|
||||
|
||||
result = ssc_cmode.get_volumes_for_specs(
|
||||
self.ssc_map, empty_dict)
|
||||
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_get_volumes_for_specs_not_a_dict(self):
|
||||
not_a_dict = False
|
||||
expected = self.ssc_map['all']
|
||||
|
||||
result = ssc_cmode.get_volumes_for_specs(
|
||||
self.ssc_map, not_a_dict)
|
||||
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_query_cl_vols_for_ssc(self):
|
||||
na_server = api.NaServer('127.0.0.1')
|
||||
na_server.set_api_version(1, 15)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
# Copyright (c) 2014 Alex Meade. All rights reserved.
|
||||
# Copyright (c) 2015 Tom Barron. 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
|
||||
|
@ -97,20 +98,21 @@ class NetAppBaseClientTestCase(test.TestCase):
|
|||
self.connection.invoke_successfully.assert_called_once_with(
|
||||
mock.ANY, True)
|
||||
|
||||
def test_create_lun_with_qos_policy_group(self):
|
||||
def test_create_lun_with_qos_policy_group_name(self):
|
||||
expected_path = '/vol/%s/%s' % (self.fake_volume, self.fake_lun)
|
||||
expected_qos_group = 'qos_1'
|
||||
expected_qos_group_name = 'qos_1'
|
||||
mock_request = mock.Mock()
|
||||
|
||||
with mock.patch.object(netapp_api.NaElement,
|
||||
'create_node_with_children',
|
||||
return_value=mock_request
|
||||
) as mock_create_node:
|
||||
self.client.create_lun(self.fake_volume,
|
||||
self.fake_lun,
|
||||
self.fake_size,
|
||||
self.fake_metadata,
|
||||
qos_policy_group=expected_qos_group)
|
||||
self.client.create_lun(
|
||||
self.fake_volume,
|
||||
self.fake_lun,
|
||||
self.fake_size,
|
||||
self.fake_metadata,
|
||||
qos_policy_group_name=expected_qos_group_name)
|
||||
|
||||
mock_create_node.assert_called_once_with(
|
||||
'lun-create-by-size',
|
||||
|
@ -119,7 +121,7 @@ class NetAppBaseClientTestCase(test.TestCase):
|
|||
'space-reservation-enabled':
|
||||
self.fake_metadata['SpaceReserved']})
|
||||
mock_request.add_new_child.assert_called_once_with(
|
||||
'qos-policy-group', expected_qos_group)
|
||||
'qos-policy-group', expected_qos_group_name)
|
||||
self.connection.invoke_successfully.assert_called_once_with(
|
||||
mock.ANY, True)
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Copyright (c) 2014 Alex Meade.
|
||||
# Copyright (c) 2015 Dustin Schoenbrun.
|
||||
# All rights reserved.
|
||||
# Copyright (c) 2014 Alex Meade. All rights reserved.
|
||||
# Copyright (c) 2015 Dustin Schoenbrun. All rights reserved.
|
||||
# Copyright (c) 2015 Tom Barron. 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
|
||||
|
@ -22,8 +22,10 @@ import six
|
|||
|
||||
from cinder import exception
|
||||
from cinder import test
|
||||
|
||||
from cinder.tests.unit.volume.drivers.netapp.dataontap.client import (
|
||||
fakes as fake)
|
||||
fakes as fake_client)
|
||||
from cinder.tests.unit.volume.drivers.netapp.dataontap import fakes as fake
|
||||
from cinder.volume.drivers.netapp.dataontap.client import (
|
||||
api as netapp_api)
|
||||
from cinder.volume.drivers.netapp.dataontap.client import client_cmode
|
||||
|
@ -53,6 +55,7 @@ class NetAppCmodeClientTestCase(test.TestCase):
|
|||
self.vserver = CONNECTION_INFO['vserver']
|
||||
self.fake_volume = six.text_type(uuid.uuid4())
|
||||
self.fake_lun = six.text_type(uuid.uuid4())
|
||||
self.mock_send_request = self.mock_object(self.client, 'send_request')
|
||||
|
||||
def tearDown(self):
|
||||
super(NetAppCmodeClientTestCase, self).tearDown()
|
||||
|
@ -414,7 +417,10 @@ class NetAppCmodeClientTestCase(test.TestCase):
|
|||
self.assertSetEqual(igroups, expected)
|
||||
|
||||
def test_clone_lun(self):
|
||||
self.client.clone_lun('volume', 'fakeLUN', 'newFakeLUN')
|
||||
self.client.clone_lun(
|
||||
'volume', 'fakeLUN', 'newFakeLUN',
|
||||
qos_policy_group_name=fake.QOS_POLICY_GROUP_NAME)
|
||||
|
||||
self.assertEqual(1, self.connection.invoke_successfully.call_count)
|
||||
|
||||
def test_clone_lun_multiple_zapi_calls(self):
|
||||
|
@ -481,28 +487,196 @@ class NetAppCmodeClientTestCase(test.TestCase):
|
|||
self.assertEqual(1, len(lun))
|
||||
|
||||
def test_file_assign_qos(self):
|
||||
expected_flex_vol = "fake_flex_vol"
|
||||
expected_policy_group = "fake_policy_group"
|
||||
expected_file_path = "fake_file_path"
|
||||
|
||||
self.client.file_assign_qos(expected_flex_vol, expected_policy_group,
|
||||
expected_file_path)
|
||||
api_args = {
|
||||
'volume': fake.FLEXVOL,
|
||||
'qos-policy-group-name': fake.QOS_POLICY_GROUP_NAME,
|
||||
'file': fake.NFS_FILE_PATH,
|
||||
'vserver': self.vserver
|
||||
}
|
||||
|
||||
__, _args, __ = self.connection.invoke_successfully.mock_calls[0]
|
||||
actual_request = _args[0]
|
||||
actual_flex_vol = actual_request.get_child_by_name('volume') \
|
||||
.get_content()
|
||||
actual_policy_group = actual_request \
|
||||
.get_child_by_name('qos-policy-group-name').get_content()
|
||||
actual_file_path = actual_request.get_child_by_name('file') \
|
||||
.get_content()
|
||||
actual_vserver = actual_request.get_child_by_name('vserver') \
|
||||
.get_content()
|
||||
self.client.file_assign_qos(
|
||||
fake.FLEXVOL, fake.QOS_POLICY_GROUP_NAME, fake.NFS_FILE_PATH)
|
||||
|
||||
self.assertEqual(expected_flex_vol, actual_flex_vol)
|
||||
self.assertEqual(expected_policy_group, actual_policy_group)
|
||||
self.assertEqual(expected_file_path, actual_file_path)
|
||||
self.assertEqual(self.vserver, actual_vserver)
|
||||
self.mock_send_request.assert_has_calls([
|
||||
mock.call('file-assign-qos', api_args, False)])
|
||||
|
||||
def test_set_lun_qos_policy_group(self):
|
||||
|
||||
api_args = {
|
||||
'path': fake.LUN_PATH,
|
||||
'qos-policy-group': fake.QOS_POLICY_GROUP_NAME,
|
||||
}
|
||||
|
||||
self.client.set_lun_qos_policy_group(
|
||||
fake.LUN_PATH, fake.QOS_POLICY_GROUP_NAME)
|
||||
|
||||
self.mock_send_request.assert_has_calls([
|
||||
mock.call('lun-set-qos-policy-group', api_args)])
|
||||
|
||||
def test_provision_qos_policy_group_no_qos_policy_group_info(self):
|
||||
|
||||
self.client.provision_qos_policy_group(qos_policy_group_info=None)
|
||||
|
||||
self.assertEqual(0, self.connection.qos_policy_group_create.call_count)
|
||||
|
||||
def test_provision_qos_policy_group_legacy_qos_policy_group_info(self):
|
||||
|
||||
self.client.provision_qos_policy_group(
|
||||
qos_policy_group_info=fake.QOS_POLICY_GROUP_INFO_LEGACY)
|
||||
|
||||
self.assertEqual(0, self.connection.qos_policy_group_create.call_count)
|
||||
|
||||
def test_provision_qos_policy_group_with_qos_spec(self):
|
||||
|
||||
self.mock_object(self.client, 'qos_policy_group_create')
|
||||
|
||||
self.client.provision_qos_policy_group(fake.QOS_POLICY_GROUP_INFO)
|
||||
|
||||
self.client.qos_policy_group_create.assert_has_calls([
|
||||
mock.call(fake.QOS_POLICY_GROUP_NAME, fake.MAX_THROUGHPUT)])
|
||||
|
||||
def test_qos_policy_group_create(self):
|
||||
|
||||
api_args = {
|
||||
'policy-group': fake.QOS_POLICY_GROUP_NAME,
|
||||
'max-throughput': fake.MAX_THROUGHPUT,
|
||||
'vserver': self.vserver,
|
||||
}
|
||||
|
||||
self.client.qos_policy_group_create(
|
||||
fake.QOS_POLICY_GROUP_NAME, fake.MAX_THROUGHPUT)
|
||||
|
||||
self.mock_send_request.assert_has_calls([
|
||||
mock.call('qos-policy-group-create', api_args, False)])
|
||||
|
||||
def test_qos_policy_group_delete(self):
|
||||
|
||||
api_args = {
|
||||
'policy-group': fake.QOS_POLICY_GROUP_NAME
|
||||
}
|
||||
|
||||
self.client.qos_policy_group_delete(
|
||||
fake.QOS_POLICY_GROUP_NAME)
|
||||
|
||||
self.mock_send_request.assert_has_calls([
|
||||
mock.call('qos-policy-group-delete', api_args, False)])
|
||||
|
||||
def test_qos_policy_group_rename(self):
|
||||
|
||||
new_name = 'new-' + fake.QOS_POLICY_GROUP_NAME
|
||||
api_args = {
|
||||
'policy-group-name': fake.QOS_POLICY_GROUP_NAME,
|
||||
'new-name': new_name,
|
||||
}
|
||||
|
||||
self.client.qos_policy_group_rename(
|
||||
fake.QOS_POLICY_GROUP_NAME, new_name)
|
||||
|
||||
self.mock_send_request.assert_has_calls([
|
||||
mock.call('qos-policy-group-rename', api_args, False)])
|
||||
|
||||
def test_mark_qos_policy_group_for_deletion_no_qos_policy_group_info(self):
|
||||
|
||||
mock_rename = self.mock_object(self.client, 'qos_policy_group_rename')
|
||||
mock_remove = self.mock_object(self.client,
|
||||
'remove_unused_qos_policy_groups')
|
||||
|
||||
self.client.mark_qos_policy_group_for_deletion(
|
||||
qos_policy_group_info=None)
|
||||
|
||||
self.assertEqual(0, mock_rename.call_count)
|
||||
self.assertEqual(0, mock_remove.call_count)
|
||||
|
||||
def test_mark_qos_policy_group_for_deletion_legacy_qos_policy(self):
|
||||
|
||||
mock_rename = self.mock_object(self.client, 'qos_policy_group_rename')
|
||||
mock_remove = self.mock_object(self.client,
|
||||
'remove_unused_qos_policy_groups')
|
||||
|
||||
self.client.mark_qos_policy_group_for_deletion(
|
||||
qos_policy_group_info=fake.QOS_POLICY_GROUP_INFO_LEGACY)
|
||||
|
||||
self.assertEqual(0, mock_rename.call_count)
|
||||
self.assertEqual(1, mock_remove.call_count)
|
||||
|
||||
def test_mark_qos_policy_group_for_deletion_w_qos_spec(self):
|
||||
|
||||
mock_rename = self.mock_object(self.client, 'qos_policy_group_rename')
|
||||
mock_remove = self.mock_object(self.client,
|
||||
'remove_unused_qos_policy_groups')
|
||||
mock_log = self.mock_object(client_cmode.LOG, 'warning')
|
||||
new_name = 'deleted_cinder_%s' % fake.QOS_POLICY_GROUP_NAME
|
||||
|
||||
self.client.mark_qos_policy_group_for_deletion(
|
||||
qos_policy_group_info=fake.QOS_POLICY_GROUP_INFO)
|
||||
|
||||
mock_rename.assert_has_calls([
|
||||
mock.call(fake.QOS_POLICY_GROUP_NAME, new_name)])
|
||||
self.assertEqual(0, mock_log.call_count)
|
||||
self.assertEqual(1, mock_remove.call_count)
|
||||
|
||||
def test_mark_qos_policy_group_for_deletion_exception_path(self):
|
||||
|
||||
mock_rename = self.mock_object(self.client, 'qos_policy_group_rename')
|
||||
mock_rename.side_effect = netapp_api.NaApiError
|
||||
mock_remove = self.mock_object(self.client,
|
||||
'remove_unused_qos_policy_groups')
|
||||
mock_log = self.mock_object(client_cmode.LOG, 'warning')
|
||||
new_name = 'deleted_cinder_%s' % fake.QOS_POLICY_GROUP_NAME
|
||||
|
||||
self.client.mark_qos_policy_group_for_deletion(
|
||||
qos_policy_group_info=fake.QOS_POLICY_GROUP_INFO)
|
||||
|
||||
mock_rename.assert_has_calls([
|
||||
mock.call(fake.QOS_POLICY_GROUP_NAME, new_name)])
|
||||
self.assertEqual(1, mock_log.call_count)
|
||||
self.assertEqual(1, mock_remove.call_count)
|
||||
|
||||
def test_remove_unused_qos_policy_groups(self):
|
||||
|
||||
mock_log = self.mock_object(client_cmode.LOG, 'debug')
|
||||
api_args = {
|
||||
'query': {
|
||||
'qos-policy-group-info': {
|
||||
'policy-group': 'deleted_cinder_*',
|
||||
'vserver': self.vserver,
|
||||
}
|
||||
},
|
||||
'max-records': 3500,
|
||||
'continue-on-failure': 'true',
|
||||
'return-success-list': 'false',
|
||||
'return-failure-list': 'false',
|
||||
}
|
||||
|
||||
self.client.remove_unused_qos_policy_groups()
|
||||
|
||||
self.mock_send_request.assert_has_calls([
|
||||
mock.call('qos-policy-group-delete-iter', api_args, False)])
|
||||
self.assertEqual(0, mock_log.call_count)
|
||||
|
||||
def test_remove_unused_qos_policy_groups_api_error(self):
|
||||
|
||||
mock_log = self.mock_object(client_cmode.LOG, 'debug')
|
||||
api_args = {
|
||||
'query': {
|
||||
'qos-policy-group-info': {
|
||||
'policy-group': 'deleted_cinder_*',
|
||||
'vserver': self.vserver,
|
||||
}
|
||||
},
|
||||
'max-records': 3500,
|
||||
'continue-on-failure': 'true',
|
||||
'return-success-list': 'false',
|
||||
'return-failure-list': 'false',
|
||||
}
|
||||
self.mock_send_request.side_effect = netapp_api.NaApiError
|
||||
|
||||
self.client.remove_unused_qos_policy_groups()
|
||||
|
||||
self.mock_send_request.assert_has_calls([
|
||||
mock.call('qos-policy-group-delete-iter', api_args, False)])
|
||||
self.assertEqual(1, mock_log.call_count)
|
||||
|
||||
@mock.patch('cinder.volume.drivers.netapp.utils.resolve_hostname',
|
||||
return_value='192.168.1.101')
|
||||
|
@ -666,8 +840,8 @@ class NetAppCmodeClientTestCase(test.TestCase):
|
|||
def test_get_operational_network_interface_addresses(self):
|
||||
expected_result = ['1.2.3.4', '99.98.97.96']
|
||||
api_response = netapp_api.NaElement(
|
||||
fake.GET_OPERATIONAL_NETWORK_INTERFACE_ADDRESSES_RESPONSE)
|
||||
self.connection.invoke_successfully.return_value = api_response
|
||||
fake_client.GET_OPERATIONAL_NETWORK_INTERFACE_ADDRESSES_RESPONSE)
|
||||
self.mock_send_request.return_value = api_response
|
||||
|
||||
address_list = (
|
||||
self.client.get_operational_network_interface_addresses())
|
||||
|
@ -678,7 +852,7 @@ class NetAppCmodeClientTestCase(test.TestCase):
|
|||
expected_total_size = 1000
|
||||
expected_available_size = 750
|
||||
fake_flexvol_path = '/fake/vol'
|
||||
response = netapp_api.NaElement(
|
||||
api_response = netapp_api.NaElement(
|
||||
etree.XML("""
|
||||
<results status="passed">
|
||||
<attributes-list>
|
||||
|
@ -691,7 +865,8 @@ class NetAppCmodeClientTestCase(test.TestCase):
|
|||
</attributes-list>
|
||||
</results>""" % {'available_size': expected_available_size,
|
||||
'total_size': expected_total_size}))
|
||||
self.connection.invoke_successfully.return_value = response
|
||||
|
||||
self.mock_send_request.return_value = api_response
|
||||
|
||||
total_size, available_size = (
|
||||
self.client.get_flexvol_capacity(fake_flexvol_path))
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
# Copyright (c) - 2014, Clinton Knight. All rights reserved.
|
||||
# Copyright (c) - 2015, Tom Barron. 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
|
||||
|
@ -12,15 +13,49 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
VOLUME_ID = 'f10d1a84-9b7b-427e-8fec-63c48b509a56'
|
||||
LUN_ID = 'ee6b4cc7-477b-4016-aa0c-7127b4e3af86'
|
||||
LUN_HANDLE = 'fake_lun_handle'
|
||||
LUN_NAME = 'lun1'
|
||||
LUN_SIZE = 3
|
||||
LUN_TABLE = {LUN_NAME: None}
|
||||
SIZE = 1024
|
||||
HOST_NAME = 'fake.host.name'
|
||||
BACKEND_NAME = 'fake_backend_name'
|
||||
POOL_NAME = 'aggr1'
|
||||
EXPORT_PATH = '/fake/export/path'
|
||||
NFS_SHARE = '192.168.99.24:%s' % EXPORT_PATH
|
||||
HOST_STRING = '%s@%s#%s' % (HOST_NAME, BACKEND_NAME, POOL_NAME)
|
||||
NFS_HOST_STRING = '%s@%s#%s' % (HOST_NAME, BACKEND_NAME, NFS_SHARE)
|
||||
FLEXVOL = 'openstack-flexvol'
|
||||
NFS_FILE_PATH = 'nfsvol'
|
||||
PATH = '/vol/%s/%s' % (POOL_NAME, LUN_NAME)
|
||||
LUN_METADATA = {
|
||||
'OsType': None,
|
||||
'SpaceReserved': 'true',
|
||||
'Path': PATH,
|
||||
'Qtree': None,
|
||||
'Volume': POOL_NAME,
|
||||
}
|
||||
VOLUME = {
|
||||
'name': LUN_NAME,
|
||||
'size': SIZE,
|
||||
'id': VOLUME_ID,
|
||||
'host': HOST_STRING,
|
||||
}
|
||||
NFS_VOLUME = {
|
||||
'name': NFS_FILE_PATH,
|
||||
'size': SIZE,
|
||||
'id': VOLUME_ID,
|
||||
'host': NFS_HOST_STRING,
|
||||
}
|
||||
|
||||
VOLUME = 'f10d1a84-9b7b-427e-8fec-63c48b509a56'
|
||||
LUN = 'ee6b4cc7-477b-4016-aa0c-7127b4e3af86'
|
||||
SIZE = '1024'
|
||||
METADATA = {'OsType': 'linux', 'SpaceReserved': 'true'}
|
||||
NETAPP_VOLUME = 'fake_netapp_volume'
|
||||
|
||||
UUID1 = '12345678-1234-5678-1234-567812345678'
|
||||
LUN1 = '/vol/vol0/lun1'
|
||||
VSERVER1_NAME = 'openstack-vserver'
|
||||
LUN_PATH = '/vol/vol0/%s' % LUN_NAME
|
||||
|
||||
VSERVER_NAME = 'openstack-vserver'
|
||||
|
||||
FC_VOLUME = {'name': 'fake_volume'}
|
||||
|
||||
|
@ -78,7 +113,8 @@ IGROUP1 = {
|
|||
}
|
||||
|
||||
ISCSI_VOLUME = {
|
||||
'name': 'fake_volume', 'id': 'fake_id',
|
||||
'name': 'fake_volume',
|
||||
'id': 'fake_id',
|
||||
'provider_auth': 'fake provider auth',
|
||||
}
|
||||
|
||||
|
@ -112,11 +148,8 @@ ISCSI_TARGET_DETAILS_LIST = [
|
|||
{'address': '99.98.97.96', 'port': '3260'},
|
||||
]
|
||||
|
||||
HOSTNAME = 'fake.host.com'
|
||||
IPV4_ADDRESS = '192.168.14.2'
|
||||
IPV6_ADDRESS = 'fe80::6e40:8ff:fe8a:130'
|
||||
EXPORT_PATH = '/fake/export/path'
|
||||
NFS_SHARE = HOSTNAME + ':' + EXPORT_PATH
|
||||
NFS_SHARE_IPV4 = IPV4_ADDRESS + ':' + EXPORT_PATH
|
||||
NFS_SHARE_IPV6 = IPV6_ADDRESS + ':' + EXPORT_PATH
|
||||
|
||||
|
@ -124,3 +157,52 @@ RESERVED_PERCENTAGE = 7
|
|||
TOTAL_BYTES = 4797892092432
|
||||
AVAILABLE_BYTES = 13479932478
|
||||
CAPACITY_VALUES = (TOTAL_BYTES, AVAILABLE_BYTES)
|
||||
|
||||
IGROUP1 = {'initiator-group-os-type': 'linux',
|
||||
'initiator-group-type': 'fcp',
|
||||
'initiator-group-name': IGROUP1_NAME}
|
||||
|
||||
QOS_SPECS = {}
|
||||
EXTRA_SPECS = {}
|
||||
MAX_THROUGHPUT = '21734278B/s'
|
||||
QOS_POLICY_GROUP_NAME = 'fake_qos_policy_group_name'
|
||||
|
||||
QOS_POLICY_GROUP_INFO_LEGACY = {
|
||||
'legacy': 'legacy-' + QOS_POLICY_GROUP_NAME,
|
||||
'spec': None,
|
||||
}
|
||||
|
||||
QOS_POLICY_GROUP_SPEC = {
|
||||
'max_throughput': MAX_THROUGHPUT,
|
||||
'policy_name': QOS_POLICY_GROUP_NAME,
|
||||
}
|
||||
|
||||
QOS_POLICY_GROUP_INFO = {'legacy': None, 'spec': QOS_POLICY_GROUP_SPEC}
|
||||
|
||||
CLONE_SOURCE_NAME = 'fake_clone_source_name'
|
||||
CLONE_SOURCE_ID = 'fake_clone_source_id'
|
||||
CLONE_SOURCE_SIZE = 1024
|
||||
|
||||
CLONE_SOURCE = {
|
||||
'size': CLONE_SOURCE_SIZE,
|
||||
'name': CLONE_SOURCE_NAME,
|
||||
'id': CLONE_SOURCE_ID,
|
||||
}
|
||||
|
||||
CLONE_DESTINATION_NAME = 'fake_clone_destination_name'
|
||||
CLONE_DESTINATION_SIZE = 1041
|
||||
CLONE_DESTINATION_ID = 'fake_clone_destination_id'
|
||||
|
||||
CLONE_DESTINATION = {
|
||||
'size': CLONE_DESTINATION_SIZE,
|
||||
'name': CLONE_DESTINATION_NAME,
|
||||
'id': CLONE_DESTINATION_ID,
|
||||
}
|
||||
|
||||
SNAPSHOT = {
|
||||
'name': 'fake_snapshot_name',
|
||||
'volume_size': SIZE,
|
||||
'volume_id': 'fake_volume_id',
|
||||
}
|
||||
|
||||
VOLUME_REF = {'name': 'fake_vref_name', 'size': 42}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
# Copyright (c) 2014 Alex Meade. All rights reserved.
|
||||
# Copyright (c) 2014 Clinton Knight. All rights reserved.
|
||||
# Copyright (c) 2015 Tom Barron. 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
|
||||
|
@ -305,6 +306,14 @@ class NetAppBlockStorage7modeLibraryTestCase(test.TestCase):
|
|||
'/vol/fake/fakeLUN', '/vol/fake/newFakeLUN', 'fakeLUN',
|
||||
'newFakeLUN', 'true', block_count=0, dest_block=0, src_block=0)
|
||||
|
||||
def test_clone_lun_qos_supplied(self):
|
||||
"""Test for qos supplied in clone lun invocation."""
|
||||
self.assertRaises(exception.VolumeDriverException,
|
||||
self.library._clone_lun,
|
||||
'fakeLUN',
|
||||
'newFakeLUN',
|
||||
qos_policy_group_name=fake.QOS_POLICY_GROUP_NAME)
|
||||
|
||||
def test_get_fc_target_wwpns(self):
|
||||
ports1 = [fake.FC_FORMATTED_TARGET_WWPNS[0],
|
||||
fake.FC_FORMATTED_TARGET_WWPNS[1]]
|
||||
|
@ -347,23 +356,52 @@ class NetAppBlockStorage7modeLibraryTestCase(test.TestCase):
|
|||
def test_create_lun(self):
|
||||
self.library.vol_refresh_voluntary = False
|
||||
|
||||
self.library._create_lun(fake.VOLUME, fake.LUN,
|
||||
fake.SIZE, fake.METADATA)
|
||||
self.library._create_lun(fake.VOLUME_ID, fake.LUN_ID,
|
||||
fake.LUN_SIZE, fake.LUN_METADATA)
|
||||
|
||||
self.library.zapi_client.create_lun.assert_called_once_with(
|
||||
fake.VOLUME, fake.LUN, fake.SIZE, fake.METADATA, None)
|
||||
fake.VOLUME_ID, fake.LUN_ID, fake.LUN_SIZE, fake.LUN_METADATA,
|
||||
None)
|
||||
self.assertTrue(self.library.vol_refresh_voluntary)
|
||||
|
||||
@mock.patch.object(na_utils, 'get_volume_extra_specs')
|
||||
def test_check_volume_type_for_lun_qos_not_supported(self, get_specs):
|
||||
get_specs.return_value = {'specs': 's',
|
||||
'netapp:qos_policy_group': 'qos'}
|
||||
mock_lun = block_base.NetAppLun('handle', 'name', '1',
|
||||
{'Volume': 'name', 'Path': '/vol/lun'})
|
||||
def test_create_lun_with_qos_policy_group(self):
|
||||
self.assertRaises(exception.VolumeDriverException,
|
||||
self.library._create_lun, fake.VOLUME_ID,
|
||||
fake.LUN_ID, fake.LUN_SIZE, fake.LUN_METADATA,
|
||||
qos_policy_group_name=fake.QOS_POLICY_GROUP_NAME)
|
||||
|
||||
def test_check_volume_type_for_lun_legacy_qos_not_supported(self):
|
||||
mock_get_volume_type = self.mock_object(na_utils,
|
||||
'get_volume_type_from_volume')
|
||||
|
||||
self.assertRaises(exception.ManageExistingVolumeTypeMismatch,
|
||||
self.library._check_volume_type_for_lun,
|
||||
{'vol': 'vol'}, mock_lun, {'ref': 'ref'})
|
||||
get_specs.assert_called_once_with({'vol': 'vol'})
|
||||
na_fakes.VOLUME, {}, {}, na_fakes.LEGACY_EXTRA_SPECS)
|
||||
|
||||
self.assertEqual(0, mock_get_volume_type.call_count)
|
||||
|
||||
def test_check_volume_type_for_lun_no_volume_type(self):
|
||||
mock_get_volume_type = self.mock_object(na_utils,
|
||||
'get_volume_type_from_volume')
|
||||
mock_get_volume_type.return_value = None
|
||||
mock_get_backend_spec = self.mock_object(
|
||||
na_utils, 'get_backend_qos_spec_from_volume_type')
|
||||
|
||||
self.library._check_volume_type_for_lun(na_fakes.VOLUME, {}, {}, None)
|
||||
|
||||
self.assertEqual(0, mock_get_backend_spec.call_count)
|
||||
|
||||
def test_check_volume_type_for_lun_qos_spec_not_supported(self):
|
||||
mock_get_volume_type = self.mock_object(na_utils,
|
||||
'get_volume_type_from_volume')
|
||||
mock_get_volume_type.return_value = na_fakes.VOLUME_TYPE
|
||||
mock_get_backend_spec = self.mock_object(
|
||||
na_utils, 'get_backend_qos_spec_from_volume_type')
|
||||
mock_get_backend_spec.return_value = na_fakes.QOS_SPEC
|
||||
|
||||
self.assertRaises(exception.ManageExistingVolumeTypeMismatch,
|
||||
self.library._check_volume_type_for_lun,
|
||||
na_fakes.VOLUME, {}, {}, na_fakes.EXTRA_SPECS)
|
||||
|
||||
def test_get_preferred_target_from_list(self):
|
||||
|
||||
|
@ -371,3 +409,54 @@ class NetAppBlockStorage7modeLibraryTestCase(test.TestCase):
|
|||
fake.ISCSI_TARGET_DETAILS_LIST)
|
||||
|
||||
self.assertEqual(fake.ISCSI_TARGET_DETAILS_LIST[0], result)
|
||||
|
||||
def test_mark_qos_policy_group_for_deletion(self):
|
||||
result = self.library._mark_qos_policy_group_for_deletion(
|
||||
fake.QOS_POLICY_GROUP_INFO)
|
||||
|
||||
self.assertEqual(None, result)
|
||||
|
||||
def test_setup_qos_for_volume(self):
|
||||
result = self.library._setup_qos_for_volume(fake.VOLUME,
|
||||
fake.EXTRA_SPECS)
|
||||
|
||||
self.assertEqual(None, result)
|
||||
|
||||
def test_manage_existing_lun_same_name(self):
|
||||
mock_lun = block_base.NetAppLun('handle', 'name', '1',
|
||||
{'Path': '/vol/vol1/name'})
|
||||
self.library._get_existing_vol_with_manage_ref = mock.Mock(
|
||||
return_value=mock_lun)
|
||||
self.mock_object(na_utils, 'get_volume_extra_specs')
|
||||
self.mock_object(na_utils, 'log_extra_spec_warnings')
|
||||
self.library._check_volume_type_for_lun = mock.Mock()
|
||||
self.library._add_lun_to_table = mock.Mock()
|
||||
self.zapi_client.move_lun = mock.Mock()
|
||||
|
||||
self.library.manage_existing({'name': 'name'}, {'ref': 'ref'})
|
||||
|
||||
self.library._get_existing_vol_with_manage_ref.assert_called_once_with(
|
||||
{'ref': 'ref'})
|
||||
self.assertEqual(1, self.library._check_volume_type_for_lun.call_count)
|
||||
self.assertEqual(1, self.library._add_lun_to_table.call_count)
|
||||
self.assertEqual(0, self.zapi_client.move_lun.call_count)
|
||||
|
||||
def test_manage_existing_lun_new_path(self):
|
||||
mock_lun = block_base.NetAppLun(
|
||||
'handle', 'name', '1', {'Path': '/vol/vol1/name'})
|
||||
self.library._get_existing_vol_with_manage_ref = mock.Mock(
|
||||
return_value=mock_lun)
|
||||
self.mock_object(na_utils, 'get_volume_extra_specs')
|
||||
self.mock_object(na_utils, 'log_extra_spec_warnings')
|
||||
self.library._check_volume_type_for_lun = mock.Mock()
|
||||
self.library._add_lun_to_table = mock.Mock()
|
||||
self.zapi_client.move_lun = mock.Mock()
|
||||
|
||||
self.library.manage_existing({'name': 'volume'}, {'ref': 'ref'})
|
||||
|
||||
self.assertEqual(
|
||||
2, self.library._get_existing_vol_with_manage_ref.call_count)
|
||||
self.assertEqual(1, self.library._check_volume_type_for_lun.call_count)
|
||||
self.assertEqual(1, self.library._add_lun_to_table.call_count)
|
||||
self.zapi_client.move_lun.assert_called_once_with(
|
||||
'/vol/vol1/name', '/vol/vol1/volume')
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# Copyright (c) 2014 Alex Meade. All rights reserved.
|
||||
# Copyright (c) 2014 Clinton Knight. All rights reserved.
|
||||
# Copyright (c) 2014 Andrew Kerr. All rights reserved.
|
||||
# Copyright (c) 2015 Tom Barron. All rights reserved.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
|
@ -18,11 +19,11 @@
|
|||
Mock unit tests for the NetApp block storage library
|
||||
"""
|
||||
|
||||
|
||||
import copy
|
||||
import uuid
|
||||
|
||||
import mock
|
||||
from oslo_utils import units
|
||||
|
||||
from cinder import exception
|
||||
from cinder.i18n import _
|
||||
|
@ -31,6 +32,7 @@ from cinder.tests.unit.volume.drivers.netapp.dataontap import fakes as fake
|
|||
from cinder.volume.drivers.netapp.dataontap import block_base
|
||||
from cinder.volume.drivers.netapp.dataontap.client import api as netapp_api
|
||||
from cinder.volume.drivers.netapp import utils as na_utils
|
||||
from cinder.volume import utils as volume_utils
|
||||
|
||||
|
||||
class NetAppBlockStorageLibraryTestCase(test.TestCase):
|
||||
|
@ -69,29 +71,59 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase):
|
|||
pool = self.library.get_pool({'name': 'volume-fake-uuid'})
|
||||
self.assertEqual(pool, None)
|
||||
|
||||
@mock.patch.object(block_base.NetAppBlockStorageLibrary,
|
||||
'_create_lun', mock.Mock())
|
||||
@mock.patch.object(block_base.NetAppBlockStorageLibrary,
|
||||
'_create_lun_handle', mock.Mock())
|
||||
@mock.patch.object(block_base.NetAppBlockStorageLibrary,
|
||||
'_add_lun_to_table', mock.Mock())
|
||||
@mock.patch.object(na_utils, 'get_volume_extra_specs',
|
||||
mock.Mock(return_value=None))
|
||||
@mock.patch.object(block_base, 'LOG', mock.Mock())
|
||||
def test_create_volume(self):
|
||||
self.library.zapi_client.get_lun_by_args.return_value = ['lun']
|
||||
self.library.create_volume({'name': 'lun1', 'size': 100,
|
||||
'id': uuid.uuid4(),
|
||||
'host': 'hostname@backend#vol1'})
|
||||
volume_size_in_bytes = int(fake.SIZE) * units.Gi
|
||||
self.mock_object(na_utils, 'get_volume_extra_specs')
|
||||
self.mock_object(na_utils, 'log_extra_spec_warnings')
|
||||
self.mock_object(block_base, 'LOG')
|
||||
self.mock_object(volume_utils, 'extract_host', mock.Mock(
|
||||
return_value=fake.POOL_NAME))
|
||||
self.mock_object(self.library, '_setup_qos_for_volume',
|
||||
mock.Mock(return_value=None))
|
||||
self.mock_object(self.library, '_create_lun')
|
||||
self.mock_object(self.library, '_create_lun_handle')
|
||||
self.mock_object(self.library, '_add_lun_to_table')
|
||||
self.mock_object(self.library, '_mark_qos_policy_group_for_deletion')
|
||||
|
||||
self.library.create_volume(fake.VOLUME)
|
||||
|
||||
self.library._create_lun.assert_called_once_with(
|
||||
'vol1', 'lun1', 107374182400, mock.ANY, None)
|
||||
self.assertEqual(0, block_base.LOG.warning.call_count)
|
||||
fake.POOL_NAME, fake.LUN_NAME, volume_size_in_bytes,
|
||||
fake.LUN_METADATA, None)
|
||||
self.assertEqual(0, self.library.
|
||||
_mark_qos_policy_group_for_deletion.call_count)
|
||||
self.assertEqual(0, block_base.LOG.error.call_count)
|
||||
|
||||
def test_create_volume_no_pool(self):
|
||||
self.mock_object(volume_utils, 'extract_host', mock.Mock(
|
||||
return_value=None))
|
||||
|
||||
self.assertRaises(exception.InvalidHost, self.library.create_volume,
|
||||
fake.VOLUME)
|
||||
|
||||
def test_create_volume_exception_path(self):
|
||||
self.mock_object(block_base, 'LOG')
|
||||
self.mock_object(na_utils, 'get_volume_extra_specs')
|
||||
self.mock_object(self.library, '_setup_qos_for_volume',
|
||||
mock.Mock(return_value=None))
|
||||
self.mock_object(self.library, '_create_lun', mock.Mock(
|
||||
side_effect=Exception))
|
||||
self.mock_object(self.library, '_mark_qos_policy_group_for_deletion')
|
||||
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self.library.create_volume, fake.VOLUME)
|
||||
|
||||
self.assertEqual(1, self.library.
|
||||
_mark_qos_policy_group_for_deletion.call_count)
|
||||
self.assertEqual(1, block_base.LOG.exception.call_count)
|
||||
|
||||
def test_create_volume_no_pool_provided_by_scheduler(self):
|
||||
fake_volume = copy.deepcopy(fake.VOLUME)
|
||||
# Set up fake volume whose 'host' field is missing pool information.
|
||||
fake_volume['host'] = '%s@%s' % (fake.HOST_NAME, fake.BACKEND_NAME)
|
||||
|
||||
self.assertRaises(exception.InvalidHost, self.library.create_volume,
|
||||
{'name': 'lun1', 'size': 100,
|
||||
'id': uuid.uuid4(),
|
||||
'host': 'hostname@backend'}) # missing pool
|
||||
fake_volume)
|
||||
|
||||
@mock.patch.object(block_base.NetAppBlockStorageLibrary,
|
||||
'_get_lun_attr')
|
||||
|
@ -101,7 +133,7 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase):
|
|||
os = 'linux'
|
||||
protocol = 'fcp'
|
||||
self.library.host_type = 'linux'
|
||||
mock_get_lun_attr.return_value = {'Path': fake.LUN1, 'OsType': os}
|
||||
mock_get_lun_attr.return_value = {'Path': fake.LUN_PATH, 'OsType': os}
|
||||
mock_get_or_create_igroup.return_value = (fake.IGROUP1_NAME, os,
|
||||
'iscsi')
|
||||
self.zapi_client.map_lun.return_value = '1'
|
||||
|
@ -114,7 +146,7 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase):
|
|||
mock_get_or_create_igroup.assert_called_once_with(
|
||||
fake.FC_FORMATTED_INITIATORS, protocol, os)
|
||||
self.zapi_client.map_lun.assert_called_once_with(
|
||||
fake.LUN1, fake.IGROUP1_NAME, lun_id=None)
|
||||
fake.LUN_PATH, fake.IGROUP1_NAME, lun_id=None)
|
||||
|
||||
@mock.patch.object(block_base.NetAppBlockStorageLibrary, '_get_lun_attr')
|
||||
@mock.patch.object(block_base.NetAppBlockStorageLibrary,
|
||||
|
@ -125,7 +157,7 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase):
|
|||
os = 'windows'
|
||||
protocol = 'fcp'
|
||||
self.library.host_type = 'linux'
|
||||
mock_get_lun_attr.return_value = {'Path': fake.LUN1, 'OsType': os}
|
||||
mock_get_lun_attr.return_value = {'Path': fake.LUN_PATH, 'OsType': os}
|
||||
mock_get_or_create_igroup.return_value = (fake.IGROUP1_NAME, os,
|
||||
'iscsi')
|
||||
self.library._map_lun('fake_volume',
|
||||
|
@ -135,7 +167,7 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase):
|
|||
fake.FC_FORMATTED_INITIATORS, protocol,
|
||||
self.library.host_type)
|
||||
self.zapi_client.map_lun.assert_called_once_with(
|
||||
fake.LUN1, fake.IGROUP1_NAME, lun_id=None)
|
||||
fake.LUN_PATH, fake.IGROUP1_NAME, lun_id=None)
|
||||
self.assertEqual(1, block_base.LOG.warning.call_count)
|
||||
|
||||
@mock.patch.object(block_base.NetAppBlockStorageLibrary,
|
||||
|
@ -148,7 +180,7 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase):
|
|||
mock_get_or_create_igroup, mock_get_lun_attr):
|
||||
os = 'linux'
|
||||
protocol = 'fcp'
|
||||
mock_get_lun_attr.return_value = {'Path': fake.LUN1, 'OsType': os}
|
||||
mock_get_lun_attr.return_value = {'Path': fake.LUN_PATH, 'OsType': os}
|
||||
mock_get_or_create_igroup.return_value = (fake.IGROUP1_NAME, os,
|
||||
'iscsi')
|
||||
mock_find_mapped_lun_igroup.return_value = (fake.IGROUP1_NAME, '2')
|
||||
|
@ -159,7 +191,7 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase):
|
|||
|
||||
self.assertEqual(lun_id, '2')
|
||||
mock_find_mapped_lun_igroup.assert_called_once_with(
|
||||
fake.LUN1, fake.FC_FORMATTED_INITIATORS)
|
||||
fake.LUN_PATH, fake.FC_FORMATTED_INITIATORS)
|
||||
|
||||
@mock.patch.object(block_base.NetAppBlockStorageLibrary,
|
||||
'_get_lun_attr')
|
||||
|
@ -171,7 +203,7 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase):
|
|||
mock_get_or_create_igroup, mock_get_lun_attr):
|
||||
os = 'linux'
|
||||
protocol = 'fcp'
|
||||
mock_get_lun_attr.return_value = {'Path': fake.LUN1, 'OsType': os}
|
||||
mock_get_lun_attr.return_value = {'Path': fake.LUN_PATH, 'OsType': os}
|
||||
mock_get_or_create_igroup.return_value = (fake.IGROUP1_NAME, os,
|
||||
'iscsi')
|
||||
mock_find_mapped_lun_igroup.return_value = (None, None)
|
||||
|
@ -186,15 +218,15 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase):
|
|||
def test_unmap_lun(self, mock_find_mapped_lun_igroup):
|
||||
mock_find_mapped_lun_igroup.return_value = (fake.IGROUP1_NAME, 1)
|
||||
|
||||
self.library._unmap_lun(fake.LUN1, fake.FC_FORMATTED_INITIATORS)
|
||||
self.library._unmap_lun(fake.LUN_PATH, fake.FC_FORMATTED_INITIATORS)
|
||||
|
||||
self.zapi_client.unmap_lun.assert_called_once_with(fake.LUN1,
|
||||
self.zapi_client.unmap_lun.assert_called_once_with(fake.LUN_PATH,
|
||||
fake.IGROUP1_NAME)
|
||||
|
||||
def test_find_mapped_lun_igroup(self):
|
||||
self.assertRaises(NotImplementedError,
|
||||
self.library._find_mapped_lun_igroup,
|
||||
fake.LUN1,
|
||||
fake.LUN_PATH,
|
||||
fake.FC_FORMATTED_INITIATORS)
|
||||
|
||||
def test_has_luns_mapped_to_initiators(self):
|
||||
|
@ -279,7 +311,7 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase):
|
|||
def test_terminate_connection_fc(self, mock_get_lun_attr, mock_unmap_lun,
|
||||
mock_has_luns_mapped_to_initiators):
|
||||
|
||||
mock_get_lun_attr.return_value = {'Path': fake.LUN1}
|
||||
mock_get_lun_attr.return_value = {'Path': fake.LUN_PATH}
|
||||
mock_unmap_lun.return_value = None
|
||||
mock_has_luns_mapped_to_initiators.return_value = True
|
||||
|
||||
|
@ -287,7 +319,7 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase):
|
|||
fake.FC_CONNECTOR)
|
||||
|
||||
self.assertDictEqual(target_info, fake.FC_TARGET_INFO_EMPTY)
|
||||
mock_unmap_lun.assert_called_once_with(fake.LUN1,
|
||||
mock_unmap_lun.assert_called_once_with(fake.LUN_PATH,
|
||||
fake.FC_FORMATTED_INITIATORS)
|
||||
|
||||
@mock.patch.object(block_base.NetAppBlockStorageLibrary,
|
||||
|
@ -303,7 +335,7 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase):
|
|||
mock_has_luns_mapped_to_initiators,
|
||||
mock_build_initiator_target_map):
|
||||
|
||||
mock_get_lun_attr.return_value = {'Path': fake.LUN1}
|
||||
mock_get_lun_attr.return_value = {'Path': fake.LUN_PATH}
|
||||
mock_unmap_lun.return_value = None
|
||||
mock_has_luns_mapped_to_initiators.return_value = False
|
||||
mock_build_initiator_target_map.return_value = (fake.FC_TARGET_WWPNS,
|
||||
|
@ -346,48 +378,6 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase):
|
|||
self.assertDictEqual(fake.FC_I_T_MAP, init_targ_map)
|
||||
self.assertEqual(4, num_paths)
|
||||
|
||||
@mock.patch.object(block_base.NetAppBlockStorageLibrary,
|
||||
'_create_lun', mock.Mock())
|
||||
@mock.patch.object(block_base.NetAppBlockStorageLibrary,
|
||||
'_create_lun_handle', mock.Mock())
|
||||
@mock.patch.object(block_base.NetAppBlockStorageLibrary,
|
||||
'_add_lun_to_table', mock.Mock())
|
||||
@mock.patch.object(na_utils, 'LOG', mock.Mock())
|
||||
@mock.patch.object(na_utils, 'get_volume_extra_specs',
|
||||
mock.Mock(return_value={'netapp:raid_type': 'raid4'}))
|
||||
def test_create_volume_obsolete_extra_spec(self):
|
||||
self.library.zapi_client.get_lun_by_args.return_value = ['lun']
|
||||
|
||||
self.library.create_volume({'name': 'lun1', 'size': 100,
|
||||
'id': uuid.uuid4(),
|
||||
'host': 'hostname@backend#vol1'})
|
||||
|
||||
warn_msg = 'Extra spec %(old)s is obsolete. Use %(new)s instead.'
|
||||
na_utils.LOG.warning.assert_called_once_with(
|
||||
warn_msg, {'new': 'netapp_raid_type', 'old': 'netapp:raid_type'})
|
||||
|
||||
@mock.patch.object(block_base.NetAppBlockStorageLibrary,
|
||||
'_create_lun', mock.Mock())
|
||||
@mock.patch.object(block_base.NetAppBlockStorageLibrary,
|
||||
'_create_lun_handle', mock.Mock())
|
||||
@mock.patch.object(block_base.NetAppBlockStorageLibrary,
|
||||
'_add_lun_to_table', mock.Mock())
|
||||
@mock.patch.object(na_utils, 'LOG', mock.Mock())
|
||||
@mock.patch.object(na_utils, 'get_volume_extra_specs',
|
||||
mock.Mock(return_value={'netapp_thick_provisioned':
|
||||
'true'}))
|
||||
def test_create_volume_deprecated_extra_spec(self):
|
||||
self.library.zapi_client.get_lun_by_args.return_value = ['lun']
|
||||
|
||||
self.library.create_volume({'name': 'lun1', 'size': 100,
|
||||
'id': uuid.uuid4(),
|
||||
'host': 'hostname@backend#vol1'})
|
||||
|
||||
warn_msg = "Extra spec %(old)s is deprecated. Use %(new)s instead."
|
||||
na_utils.LOG.warning.assert_called_once_with(
|
||||
warn_msg, {'new': 'netapp_thin_provisioned',
|
||||
'old': 'netapp_thick_provisioned'})
|
||||
|
||||
@mock.patch.object(na_utils, 'check_flags')
|
||||
def test_do_setup_san_configured(self, mock_check_flags):
|
||||
self.library.configuration.netapp_lun_ostype = 'windows'
|
||||
|
@ -451,41 +441,10 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase):
|
|||
self.library._get_lun_from_table.assert_called_once_with('vol')
|
||||
self.assertEqual(1, log.call_count)
|
||||
|
||||
def test_manage_existing_lun_same_name(self):
|
||||
mock_lun = block_base.NetAppLun('handle', 'name', '1',
|
||||
{'Path': '/vol/vol1/name'})
|
||||
self.library._get_existing_vol_with_manage_ref = mock.Mock(
|
||||
return_value=mock_lun)
|
||||
self.library._check_volume_type_for_lun = mock.Mock()
|
||||
self.library._add_lun_to_table = mock.Mock()
|
||||
self.zapi_client.move_lun = mock.Mock()
|
||||
self.library.manage_existing({'name': 'name'}, {'ref': 'ref'})
|
||||
self.library._get_existing_vol_with_manage_ref.assert_called_once_with(
|
||||
{'ref': 'ref'})
|
||||
self.assertEqual(1, self.library._check_volume_type_for_lun.call_count)
|
||||
self.assertEqual(1, self.library._add_lun_to_table.call_count)
|
||||
self.assertEqual(0, self.zapi_client.move_lun.call_count)
|
||||
|
||||
def test_manage_existing_lun_new_path(self):
|
||||
mock_lun = block_base.NetAppLun(
|
||||
'handle', 'name', '1', {'Path': '/vol/vol1/name'})
|
||||
self.library._get_existing_vol_with_manage_ref = mock.Mock(
|
||||
return_value=mock_lun)
|
||||
self.library._check_volume_type_for_lun = mock.Mock()
|
||||
self.library._add_lun_to_table = mock.Mock()
|
||||
self.zapi_client.move_lun = mock.Mock()
|
||||
self.library.manage_existing({'name': 'volume'}, {'ref': 'ref'})
|
||||
self.assertEqual(
|
||||
2, self.library._get_existing_vol_with_manage_ref.call_count)
|
||||
self.assertEqual(1, self.library._check_volume_type_for_lun.call_count)
|
||||
self.assertEqual(1, self.library._add_lun_to_table.call_count)
|
||||
self.zapi_client.move_lun.assert_called_once_with(
|
||||
'/vol/vol1/name', '/vol/vol1/volume')
|
||||
|
||||
def test_check_vol_type_for_lun(self):
|
||||
self.assertRaises(NotImplementedError,
|
||||
self.library._check_volume_type_for_lun,
|
||||
'vol', 'lun', 'existing_ref')
|
||||
'vol', 'lun', 'existing_ref', {})
|
||||
|
||||
def test_is_lun_valid_on_storage(self):
|
||||
self.assertTrue(self.library._is_lun_valid_on_storage('lun'))
|
||||
|
@ -679,3 +638,128 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase):
|
|||
self.library.check_for_setup_error()
|
||||
self.library._extract_and_populate_luns.assert_called_once_with(
|
||||
['lun1'])
|
||||
|
||||
def test_delete_volume(self):
|
||||
mock_get_lun_attr = self.mock_object(self.library, '_get_lun_attr')
|
||||
mock_get_lun_attr.return_value = fake.LUN_METADATA
|
||||
self.library.zapi_client = mock.Mock()
|
||||
self.library.lun_table = fake.LUN_TABLE
|
||||
|
||||
self.library.delete_volume(fake.VOLUME)
|
||||
|
||||
mock_get_lun_attr.assert_called_once_with(
|
||||
fake.LUN_NAME, 'metadata')
|
||||
self.library.zapi_client.destroy_lun.assert_called_once_with(fake.PATH)
|
||||
|
||||
def test_delete_volume_no_metadata(self):
|
||||
self.mock_object(self.library, '_get_lun_attr', mock.Mock(
|
||||
return_value=None))
|
||||
self.library.zapi_client = mock.Mock()
|
||||
self.mock_object(self.library, 'zapi_client')
|
||||
|
||||
self.library.delete_volume(fake.VOLUME)
|
||||
|
||||
self.library._get_lun_attr.assert_called_once_with(
|
||||
fake.LUN_NAME, 'metadata')
|
||||
self.assertEqual(0, self.library.zapi_client.destroy_lun.call_count)
|
||||
self.assertEqual(0,
|
||||
self.zapi_client.
|
||||
mark_qos_policy_group_for_deletion.call_count)
|
||||
|
||||
def test_clone_source_to_destination(self):
|
||||
self.mock_object(na_utils, 'get_volume_extra_specs', mock.Mock(
|
||||
return_value=fake.EXTRA_SPECS))
|
||||
self.mock_object(self.library, '_setup_qos_for_volume', mock.Mock(
|
||||
return_value=fake.QOS_POLICY_GROUP_INFO))
|
||||
self.mock_object(self.library, '_clone_lun')
|
||||
self.mock_object(self.library, 'extend_volume')
|
||||
self.mock_object(self.library, 'delete_volume')
|
||||
self.mock_object(self.library, '_mark_qos_policy_group_for_deletion')
|
||||
|
||||
self.library._clone_source_to_destination(fake.CLONE_SOURCE,
|
||||
fake.CLONE_DESTINATION)
|
||||
|
||||
na_utils.get_volume_extra_specs.assert_called_once_with(
|
||||
fake.CLONE_DESTINATION)
|
||||
self.library._setup_qos_for_volume.assert_called_once_with(
|
||||
fake.CLONE_DESTINATION, fake.EXTRA_SPECS)
|
||||
self.library._clone_lun.assert_called_once_with(
|
||||
fake.CLONE_SOURCE_NAME, fake.CLONE_DESTINATION_NAME,
|
||||
space_reserved='true',
|
||||
qos_policy_group_name=fake.QOS_POLICY_GROUP_NAME)
|
||||
self.library.extend_volume.assert_called_once_with(
|
||||
fake.CLONE_DESTINATION, fake.CLONE_DESTINATION_SIZE,
|
||||
qos_policy_group_name=fake.QOS_POLICY_GROUP_NAME)
|
||||
self.assertEqual(0, self.library.delete_volume.call_count)
|
||||
self.assertEqual(0, self.library.
|
||||
_mark_qos_policy_group_for_deletion.call_count)
|
||||
|
||||
def test_clone_source_to_destination_exception_path(self):
|
||||
self.mock_object(na_utils, 'get_volume_extra_specs', mock.Mock(
|
||||
return_value=fake.EXTRA_SPECS))
|
||||
self.mock_object(self.library, '_setup_qos_for_volume', mock.Mock(
|
||||
return_value=fake.QOS_POLICY_GROUP_INFO))
|
||||
self.mock_object(self.library, '_clone_lun')
|
||||
self.mock_object(self.library, 'extend_volume', mock.Mock(
|
||||
side_effect=Exception))
|
||||
self.mock_object(self.library, 'delete_volume')
|
||||
self.mock_object(self.library, '_mark_qos_policy_group_for_deletion')
|
||||
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self.library._clone_source_to_destination,
|
||||
fake.CLONE_SOURCE, fake.CLONE_DESTINATION)
|
||||
|
||||
na_utils.get_volume_extra_specs.assert_called_once_with(
|
||||
fake.CLONE_DESTINATION)
|
||||
self.library._setup_qos_for_volume.assert_called_once_with(
|
||||
fake.CLONE_DESTINATION, fake.EXTRA_SPECS)
|
||||
self.library._clone_lun.assert_called_once_with(
|
||||
fake.CLONE_SOURCE_NAME, fake.CLONE_DESTINATION_NAME,
|
||||
space_reserved='true',
|
||||
qos_policy_group_name=fake.QOS_POLICY_GROUP_NAME)
|
||||
self.library.extend_volume.assert_called_once_with(
|
||||
fake.CLONE_DESTINATION, fake.CLONE_DESTINATION_SIZE,
|
||||
qos_policy_group_name=fake.QOS_POLICY_GROUP_NAME)
|
||||
self.assertEqual(1, self.library.delete_volume.call_count)
|
||||
self.assertEqual(1, self.library.
|
||||
_mark_qos_policy_group_for_deletion.call_count)
|
||||
|
||||
def test_create_lun(self):
|
||||
self.assertRaises(NotImplementedError, self.library._create_lun,
|
||||
fake.VOLUME_ID, fake.LUN_ID, fake.SIZE,
|
||||
fake.LUN_METADATA)
|
||||
|
||||
def test_clone_lun(self):
|
||||
self.assertRaises(NotImplementedError, self.library._clone_lun,
|
||||
fake.VOLUME_ID, 'new-' + fake.VOLUME_ID)
|
||||
|
||||
def test_create_volume_from_snapshot(self):
|
||||
mock_do_clone = self.mock_object(self.library,
|
||||
'_clone_source_to_destination')
|
||||
source = {
|
||||
'name': fake.SNAPSHOT['name'],
|
||||
'size': fake.SNAPSHOT['volume_size']
|
||||
}
|
||||
|
||||
self.library.create_volume_from_snapshot(fake.VOLUME, fake.SNAPSHOT)
|
||||
|
||||
mock_do_clone.assert_has_calls([
|
||||
mock.call(source, fake.VOLUME)])
|
||||
|
||||
def test_create_cloned_volume(self):
|
||||
fake_lun = block_base.NetAppLun(fake.LUN_HANDLE, fake.LUN_ID,
|
||||
fake.LUN_SIZE, fake.LUN_METADATA)
|
||||
mock_get_lun_from_table = self.mock_object(self.library,
|
||||
'_get_lun_from_table')
|
||||
mock_get_lun_from_table.return_value = fake_lun
|
||||
mock_do_clone = self.mock_object(self.library,
|
||||
'_clone_source_to_destination')
|
||||
source = {
|
||||
'name': fake_lun.name,
|
||||
'size': fake.VOLUME_REF['size']
|
||||
}
|
||||
|
||||
self.library.create_cloned_volume(fake.VOLUME, fake.VOLUME_REF)
|
||||
|
||||
mock_do_clone.assert_has_calls([
|
||||
mock.call(source, fake.VOLUME)])
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
# Copyright (c) 2014 Alex Meade. All rights reserved.
|
||||
# Copyright (c) 2014 Clinton Knight. All rights reserved.
|
||||
# Copyright (c) 2015 Tom Barron. 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
|
||||
|
@ -16,10 +17,10 @@
|
|||
Mock unit tests for the NetApp block storage C-mode library
|
||||
"""
|
||||
|
||||
|
||||
import mock
|
||||
|
||||
from cinder import exception
|
||||
from cinder.openstack.common import loopingcall
|
||||
from cinder import test
|
||||
import cinder.tests.unit.volume.drivers.netapp.dataontap.fakes as fake
|
||||
import cinder.tests.unit.volume.drivers.netapp.fakes as na_fakes
|
||||
|
@ -45,6 +46,11 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase):
|
|||
self.zapi_client = self.library.zapi_client
|
||||
self.library.vserver = mock.Mock()
|
||||
self.library.ssc_vols = None
|
||||
self.fake_lun = block_base.NetAppLun(fake.LUN_HANDLE, fake.LUN_NAME,
|
||||
fake.SIZE, None)
|
||||
self.mock_object(self.library, 'lun_table')
|
||||
self.library.lun_table = {fake.LUN_NAME: self.fake_lun}
|
||||
self.mock_object(block_base.NetAppBlockStorageLibrary, 'delete_volume')
|
||||
|
||||
def tearDown(self):
|
||||
super(NetAppBlockStorageCmodeLibraryTestCase, self).tearDown()
|
||||
|
@ -72,17 +78,20 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase):
|
|||
super_do_setup.assert_called_once_with(context)
|
||||
self.assertEqual(1, mock_check_flags.call_count)
|
||||
|
||||
@mock.patch.object(block_base.NetAppBlockStorageLibrary,
|
||||
'check_for_setup_error')
|
||||
@mock.patch.object(ssc_cmode, 'check_ssc_api_permissions')
|
||||
def test_check_for_setup_error(self, mock_check_ssc_api_permissions,
|
||||
super_check_for_setup_error):
|
||||
def test_check_for_setup_error(self):
|
||||
super_check_for_setup_error = self.mock_object(
|
||||
block_base.NetAppBlockStorageLibrary, 'check_for_setup_error')
|
||||
mock_check_ssc_api_permissions = self.mock_object(
|
||||
ssc_cmode, 'check_ssc_api_permissions')
|
||||
mock_start_periodic_tasks = self.mock_object(
|
||||
self.library, '_start_periodic_tasks')
|
||||
|
||||
self.library.check_for_setup_error()
|
||||
|
||||
super_check_for_setup_error.assert_called_once_with()
|
||||
self.assertEqual(1, super_check_for_setup_error.call_count)
|
||||
mock_check_ssc_api_permissions.assert_called_once_with(
|
||||
self.library.zapi_client)
|
||||
self.assertEqual(1, mock_start_periodic_tasks.call_count)
|
||||
|
||||
def test_find_mapped_lun_igroup(self):
|
||||
igroups = [fake.IGROUP1]
|
||||
|
@ -90,11 +99,11 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase):
|
|||
|
||||
lun_maps = [{'initiator-group': fake.IGROUP1_NAME,
|
||||
'lun-id': '1',
|
||||
'vserver': fake.VSERVER1_NAME}]
|
||||
'vserver': fake.VSERVER_NAME}]
|
||||
self.zapi_client.get_lun_map.return_value = lun_maps
|
||||
|
||||
(igroup, lun_id) = self.library._find_mapped_lun_igroup(
|
||||
fake.LUN1, fake.FC_FORMATTED_INITIATORS)
|
||||
fake.LUN_PATH, fake.FC_FORMATTED_INITIATORS)
|
||||
|
||||
self.assertEqual(fake.IGROUP1_NAME, igroup)
|
||||
self.assertEqual('1', lun_id)
|
||||
|
@ -104,11 +113,11 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase):
|
|||
|
||||
lun_maps = [{'initiator-group': fake.IGROUP1_NAME,
|
||||
'lun-id': '1',
|
||||
'vserver': fake.VSERVER1_NAME}]
|
||||
'vserver': fake.VSERVER_NAME}]
|
||||
self.zapi_client.get_lun_map.return_value = lun_maps
|
||||
|
||||
(igroup, lun_id) = self.library._find_mapped_lun_igroup(
|
||||
fake.LUN1, fake.FC_FORMATTED_INITIATORS)
|
||||
fake.LUN_PATH, fake.FC_FORMATTED_INITIATORS)
|
||||
|
||||
self.assertIsNone(igroup)
|
||||
self.assertIsNone(lun_id)
|
||||
|
@ -121,11 +130,11 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase):
|
|||
|
||||
lun_maps = [{'initiator-group': fake.IGROUP1_NAME,
|
||||
'lun-id': '1',
|
||||
'vserver': fake.VSERVER1_NAME}]
|
||||
'vserver': fake.VSERVER_NAME}]
|
||||
self.zapi_client.get_lun_map.return_value = lun_maps
|
||||
|
||||
(igroup, lun_id) = self.library._find_mapped_lun_igroup(
|
||||
fake.LUN1, fake.FC_FORMATTED_INITIATORS)
|
||||
fake.LUN_PATH, fake.FC_FORMATTED_INITIATORS)
|
||||
|
||||
self.assertIsNone(igroup)
|
||||
self.assertIsNone(lun_id)
|
||||
|
@ -138,11 +147,11 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase):
|
|||
|
||||
lun_maps = [{'initiator-group': 'igroup2',
|
||||
'lun-id': '1',
|
||||
'vserver': fake.VSERVER1_NAME}]
|
||||
'vserver': fake.VSERVER_NAME}]
|
||||
self.zapi_client.get_lun_map.return_value = lun_maps
|
||||
|
||||
(igroup, lun_id) = self.library._find_mapped_lun_igroup(
|
||||
fake.LUN1, fake.FC_FORMATTED_INITIATORS)
|
||||
fake.LUN_PATH, fake.FC_FORMATTED_INITIATORS)
|
||||
|
||||
self.assertIsNone(igroup)
|
||||
self.assertIsNone(lun_id)
|
||||
|
@ -187,7 +196,7 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase):
|
|||
|
||||
self.library.zapi_client.clone_lun.assert_called_once_with(
|
||||
'fakeLUN', 'fakeLUN', 'newFakeLUN', 'true', block_count=0,
|
||||
dest_block=0, src_block=0)
|
||||
dest_block=0, src_block=0, qos_policy_group_name=None)
|
||||
|
||||
def test_get_fc_target_wwpns(self):
|
||||
ports = [fake.FC_FORMATTED_TARGET_WWPNS[0],
|
||||
|
@ -211,57 +220,30 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase):
|
|||
def test_create_lun(self):
|
||||
self.library._update_stale_vols = mock.Mock()
|
||||
|
||||
self.library._create_lun(fake.VOLUME, fake.LUN,
|
||||
fake.SIZE, fake.METADATA)
|
||||
self.library._create_lun(fake.VOLUME_ID, fake.LUN_ID,
|
||||
fake.LUN_SIZE, fake.LUN_METADATA)
|
||||
|
||||
self.library.zapi_client.create_lun.assert_called_once_with(
|
||||
fake.VOLUME, fake.LUN, fake.SIZE, fake.METADATA, None)
|
||||
fake.VOLUME_ID, fake.LUN_ID, fake.LUN_SIZE, fake.LUN_METADATA,
|
||||
None)
|
||||
self.assertEqual(1, self.library._update_stale_vols.call_count)
|
||||
|
||||
@mock.patch.object(ssc_cmode, 'get_volumes_for_specs')
|
||||
@mock.patch.object(ssc_cmode, 'get_cluster_latest_ssc')
|
||||
@mock.patch.object(na_utils, 'get_volume_extra_specs')
|
||||
def test_check_volume_type_for_lun_fail(
|
||||
self, get_specs, get_ssc, get_vols):
|
||||
def test_check_volume_type_for_lun_fail(self, get_ssc, get_vols):
|
||||
self.library.ssc_vols = ['vol']
|
||||
get_specs.return_value = {'specs': 's'}
|
||||
fake_extra_specs = {'specs': 's'}
|
||||
get_vols.return_value = [ssc_cmode.NetAppVolume(name='name',
|
||||
vserver='vs')]
|
||||
mock_lun = block_base.NetAppLun('handle', 'name', '1',
|
||||
{'Volume': 'fake', 'Path': '/vol/lun'})
|
||||
self.assertRaises(exception.ManageExistingVolumeTypeMismatch,
|
||||
self.library._check_volume_type_for_lun,
|
||||
{'vol': 'vol'}, mock_lun, {'ref': 'ref'})
|
||||
get_specs.assert_called_once_with({'vol': 'vol'})
|
||||
{'vol': 'vol'}, mock_lun, {'ref': 'ref'},
|
||||
fake_extra_specs)
|
||||
get_vols.assert_called_with(['vol'], {'specs': 's'})
|
||||
self.assertEqual(1, get_ssc.call_count)
|
||||
|
||||
@mock.patch.object(block_cmode.LOG, 'error')
|
||||
@mock.patch.object(ssc_cmode, 'get_volumes_for_specs')
|
||||
@mock.patch.object(ssc_cmode, 'get_cluster_latest_ssc')
|
||||
@mock.patch.object(na_utils, 'get_volume_extra_specs')
|
||||
def test_check_volume_type_for_lun_qos_fail(
|
||||
self, get_specs, get_ssc, get_vols, driver_log):
|
||||
self.zapi_client.connection.set_api_version(1, 20)
|
||||
self.library.ssc_vols = ['vol']
|
||||
get_specs.return_value = {'specs': 's',
|
||||
'netapp:qos_policy_group': 'qos'}
|
||||
get_vols.return_value = [ssc_cmode.NetAppVolume(name='name',
|
||||
vserver='vs')]
|
||||
mock_lun = block_base.NetAppLun('handle', 'name', '1',
|
||||
{'Volume': 'name', 'Path': '/vol/lun'})
|
||||
self.zapi_client.set_lun_qos_policy_group = mock.Mock(
|
||||
side_effect=netapp_api.NaApiError)
|
||||
self.assertRaises(exception.ManageExistingVolumeTypeMismatch,
|
||||
self.library._check_volume_type_for_lun,
|
||||
{'vol': 'vol'}, mock_lun, {'ref': 'ref'})
|
||||
get_specs.assert_called_once_with({'vol': 'vol'})
|
||||
get_vols.assert_called_with(['vol'], {'specs': 's'})
|
||||
self.assertEqual(0, get_ssc.call_count)
|
||||
self.zapi_client.set_lun_qos_policy_group.assert_called_once_with(
|
||||
'/vol/lun', 'qos')
|
||||
self.assertEqual(1, driver_log.call_count)
|
||||
|
||||
def test_get_preferred_target_from_list(self):
|
||||
target_details_list = fake.ISCSI_TARGET_DETAILS_LIST
|
||||
operational_addresses = [
|
||||
|
@ -274,3 +256,191 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase):
|
|||
target_details_list)
|
||||
|
||||
self.assertEqual(target_details_list[2], result)
|
||||
|
||||
def test_delete_volume(self):
|
||||
self.mock_object(block_base.NetAppLun, 'get_metadata_property',
|
||||
mock.Mock(return_value=fake.POOL_NAME))
|
||||
self.mock_object(self.library, '_update_stale_vols')
|
||||
self.mock_object(na_utils, 'get_valid_qos_policy_group_info',
|
||||
mock.Mock(
|
||||
return_value=fake.QOS_POLICY_GROUP_INFO))
|
||||
self.mock_object(self.library, '_mark_qos_policy_group_for_deletion')
|
||||
|
||||
self.library.delete_volume(fake.VOLUME)
|
||||
|
||||
self.assertEqual(1,
|
||||
block_base.NetAppLun.get_metadata_property.call_count)
|
||||
block_base.NetAppBlockStorageLibrary.delete_volume\
|
||||
.assert_called_once_with(fake.VOLUME)
|
||||
na_utils.get_valid_qos_policy_group_info.assert_called_once_with(
|
||||
fake.VOLUME)
|
||||
self.library._mark_qos_policy_group_for_deletion\
|
||||
.assert_called_once_with(fake.QOS_POLICY_GROUP_INFO)
|
||||
self.assertEqual(1, self.library._update_stale_vols.call_count)
|
||||
|
||||
def test_delete_volume_no_netapp_vol(self):
|
||||
self.mock_object(block_base.NetAppLun, 'get_metadata_property',
|
||||
mock.Mock(return_value=None))
|
||||
self.mock_object(self.library, '_update_stale_vols')
|
||||
self.mock_object(na_utils, 'get_valid_qos_policy_group_info',
|
||||
mock.Mock(
|
||||
return_value=fake.QOS_POLICY_GROUP_INFO))
|
||||
self.mock_object(self.library, '_mark_qos_policy_group_for_deletion')
|
||||
|
||||
self.library.delete_volume(fake.VOLUME)
|
||||
|
||||
block_base.NetAppLun.get_metadata_property.assert_called_once_with(
|
||||
'Volume')
|
||||
block_base.NetAppBlockStorageLibrary.delete_volume\
|
||||
.assert_called_once_with(fake.VOLUME)
|
||||
self.library._mark_qos_policy_group_for_deletion\
|
||||
.assert_called_once_with(fake.QOS_POLICY_GROUP_INFO)
|
||||
self.assertEqual(0, self.library._update_stale_vols.call_count)
|
||||
|
||||
def test_delete_volume_get_valid_qos_policy_group_info_exception(self):
|
||||
self.mock_object(block_base.NetAppLun, 'get_metadata_property',
|
||||
mock.Mock(return_value=fake.NETAPP_VOLUME))
|
||||
self.mock_object(self.library, '_update_stale_vols')
|
||||
self.mock_object(na_utils, 'get_valid_qos_policy_group_info',
|
||||
mock.Mock(side_effect=exception.Invalid))
|
||||
self.mock_object(self.library, '_mark_qos_policy_group_for_deletion')
|
||||
|
||||
self.library.delete_volume(fake.VOLUME)
|
||||
|
||||
block_base.NetAppLun.get_metadata_property.assert_called_once_with(
|
||||
'Volume')
|
||||
block_base.NetAppBlockStorageLibrary.delete_volume\
|
||||
.assert_called_once_with(fake.VOLUME)
|
||||
self.library._mark_qos_policy_group_for_deletion\
|
||||
.assert_called_once_with(None)
|
||||
self.assertEqual(1, self.library._update_stale_vols.call_count)
|
||||
|
||||
def test_setup_qos_for_volume(self):
|
||||
self.mock_object(na_utils, 'get_valid_qos_policy_group_info',
|
||||
mock.Mock(
|
||||
return_value=fake.QOS_POLICY_GROUP_INFO))
|
||||
self.mock_object(self.zapi_client, 'provision_qos_policy_group')
|
||||
|
||||
result = self.library._setup_qos_for_volume(fake.VOLUME,
|
||||
fake.EXTRA_SPECS)
|
||||
|
||||
self.assertEqual(fake.QOS_POLICY_GROUP_INFO, result)
|
||||
self.zapi_client.provision_qos_policy_group.\
|
||||
assert_called_once_with(fake.QOS_POLICY_GROUP_INFO)
|
||||
|
||||
def test_setup_qos_for_volume_exception_path(self):
|
||||
self.mock_object(na_utils, 'get_valid_qos_policy_group_info',
|
||||
mock.Mock(
|
||||
side_effect=exception.Invalid))
|
||||
self.mock_object(self.zapi_client, 'provision_qos_policy_group')
|
||||
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self.library._setup_qos_for_volume, fake.VOLUME,
|
||||
fake.EXTRA_SPECS)
|
||||
|
||||
self.assertEqual(0,
|
||||
self.zapi_client.
|
||||
provision_qos_policy_group.call_count)
|
||||
|
||||
def test_mark_qos_policy_group_for_deletion(self):
|
||||
self.mock_object(self.zapi_client,
|
||||
'mark_qos_policy_group_for_deletion')
|
||||
|
||||
self.library._mark_qos_policy_group_for_deletion(
|
||||
fake.QOS_POLICY_GROUP_INFO)
|
||||
|
||||
self.zapi_client.mark_qos_policy_group_for_deletion\
|
||||
.assert_called_once_with(fake.QOS_POLICY_GROUP_INFO)
|
||||
|
||||
def test_unmanage(self):
|
||||
self.mock_object(na_utils, 'get_valid_qos_policy_group_info',
|
||||
mock.Mock(return_value=fake.QOS_POLICY_GROUP_INFO))
|
||||
self.mock_object(self.library, '_mark_qos_policy_group_for_deletion')
|
||||
self.mock_object(block_base.NetAppBlockStorageLibrary, 'unmanage')
|
||||
|
||||
self.library.unmanage(fake.VOLUME)
|
||||
|
||||
na_utils.get_valid_qos_policy_group_info.assert_called_once_with(
|
||||
fake.VOLUME)
|
||||
self.library._mark_qos_policy_group_for_deletion\
|
||||
.assert_called_once_with(fake.QOS_POLICY_GROUP_INFO)
|
||||
block_base.NetAppBlockStorageLibrary.unmanage.assert_called_once_with(
|
||||
fake.VOLUME)
|
||||
|
||||
def test_unmanage_w_invalid_qos_policy(self):
|
||||
self.mock_object(na_utils, 'get_valid_qos_policy_group_info',
|
||||
mock.Mock(side_effect=exception.Invalid))
|
||||
self.mock_object(self.library, '_mark_qos_policy_group_for_deletion')
|
||||
self.mock_object(block_base.NetAppBlockStorageLibrary, 'unmanage')
|
||||
|
||||
self.library.unmanage(fake.VOLUME)
|
||||
|
||||
na_utils.get_valid_qos_policy_group_info.assert_called_once_with(
|
||||
fake.VOLUME)
|
||||
self.library._mark_qos_policy_group_for_deletion\
|
||||
.assert_called_once_with(None)
|
||||
block_base.NetAppBlockStorageLibrary.unmanage.assert_called_once_with(
|
||||
fake.VOLUME)
|
||||
|
||||
def test_manage_existing_lun_same_name(self):
|
||||
mock_lun = block_base.NetAppLun('handle', 'name', '1',
|
||||
{'Path': '/vol/vol1/name'})
|
||||
self.library._get_existing_vol_with_manage_ref = mock.Mock(
|
||||
return_value=mock_lun)
|
||||
self.mock_object(na_utils, 'get_volume_extra_specs')
|
||||
self.mock_object(na_utils, 'log_extra_spec_warnings')
|
||||
self.library._check_volume_type_for_lun = mock.Mock()
|
||||
self.library._setup_qos_for_volume = mock.Mock()
|
||||
self.mock_object(na_utils, 'get_qos_policy_group_name_from_info',
|
||||
mock.Mock(return_value=fake.QOS_POLICY_GROUP_NAME))
|
||||
self.library._add_lun_to_table = mock.Mock()
|
||||
self.zapi_client.move_lun = mock.Mock()
|
||||
mock_set_lun_qos_policy_group = self.mock_object(
|
||||
self.zapi_client, 'set_lun_qos_policy_group')
|
||||
|
||||
self.library.manage_existing({'name': 'name'}, {'ref': 'ref'})
|
||||
|
||||
self.library._get_existing_vol_with_manage_ref.assert_called_once_with(
|
||||
{'ref': 'ref'})
|
||||
self.assertEqual(1, self.library._check_volume_type_for_lun.call_count)
|
||||
self.assertEqual(1, self.library._add_lun_to_table.call_count)
|
||||
self.assertEqual(0, self.zapi_client.move_lun.call_count)
|
||||
self.assertEqual(1, mock_set_lun_qos_policy_group.call_count)
|
||||
|
||||
def test_manage_existing_lun_new_path(self):
|
||||
mock_lun = block_base.NetAppLun(
|
||||
'handle', 'name', '1', {'Path': '/vol/vol1/name'})
|
||||
self.library._get_existing_vol_with_manage_ref = mock.Mock(
|
||||
return_value=mock_lun)
|
||||
self.mock_object(na_utils, 'get_volume_extra_specs')
|
||||
self.mock_object(na_utils, 'log_extra_spec_warnings')
|
||||
self.library._check_volume_type_for_lun = mock.Mock()
|
||||
self.library._add_lun_to_table = mock.Mock()
|
||||
self.zapi_client.move_lun = mock.Mock()
|
||||
|
||||
self.library.manage_existing({'name': 'volume'}, {'ref': 'ref'})
|
||||
|
||||
self.assertEqual(
|
||||
2, self.library._get_existing_vol_with_manage_ref.call_count)
|
||||
self.assertEqual(1, self.library._check_volume_type_for_lun.call_count)
|
||||
self.assertEqual(1, self.library._add_lun_to_table.call_count)
|
||||
self.zapi_client.move_lun.assert_called_once_with(
|
||||
'/vol/vol1/name', '/vol/vol1/volume')
|
||||
|
||||
def test_start_periodic_tasks(self):
|
||||
|
||||
mock_remove_unused_qos_policy_groups = self.mock_object(
|
||||
self.zapi_client,
|
||||
'remove_unused_qos_policy_groups')
|
||||
|
||||
harvest_qos_periodic_task = mock.Mock()
|
||||
mock_loopingcall = self.mock_object(
|
||||
loopingcall,
|
||||
'FixedIntervalLoopingCall',
|
||||
mock.Mock(side_effect=[harvest_qos_periodic_task]))
|
||||
|
||||
self.library._start_periodic_tasks()
|
||||
|
||||
mock_loopingcall.assert_has_calls([
|
||||
mock.call(mock_remove_unused_qos_policy_groups)])
|
||||
self.assertTrue(harvest_qos_periodic_task.start.called)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
# Copyright (c) 2014 Andrew Kerr. All rights reserved.
|
||||
# Copyright (c) 2015 Tom Barron. All rights reserved.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
|
@ -16,10 +17,14 @@
|
|||
Mock unit tests for the NetApp nfs storage driver
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
import copy
|
||||
import mock
|
||||
from os_brick.remotefs import remotefs as remotefs_brick
|
||||
from oslo_utils import units
|
||||
|
||||
from cinder import exception
|
||||
from cinder import test
|
||||
from cinder.tests.unit.volume.drivers.netapp.dataontap import fakes as fake
|
||||
from cinder import utils
|
||||
|
@ -43,6 +48,7 @@ class NetAppNfsDriverTestCase(test.TestCase):
|
|||
with mock.patch.object(remotefs_brick, 'RemoteFsClient',
|
||||
return_value=mock.Mock()):
|
||||
self.driver = nfs_base.NetAppNfsDriver(**kwargs)
|
||||
self.driver.ssc_enabled = False
|
||||
|
||||
@mock.patch.object(nfs.NfsDriver, 'do_setup')
|
||||
@mock.patch.object(na_utils, 'check_flags')
|
||||
|
@ -98,3 +104,189 @@ class NetAppNfsDriverTestCase(test.TestCase):
|
|||
self.assertEqual(expected, result)
|
||||
get_capacity.assert_has_calls([
|
||||
mock.call(fake.EXPORT_PATH)])
|
||||
|
||||
def test_create_volume(self):
|
||||
self.mock_object(self.driver, '_ensure_shares_mounted')
|
||||
self.mock_object(na_utils, 'get_volume_extra_specs')
|
||||
self.mock_object(self.driver, '_do_create_volume')
|
||||
self.mock_object(self.driver, '_do_qos_for_volume')
|
||||
update_ssc = self.mock_object(self.driver, '_update_stale_vols')
|
||||
expected = {'provider_location': fake.NFS_SHARE}
|
||||
|
||||
result = self.driver.create_volume(fake.NFS_VOLUME)
|
||||
|
||||
self.assertEqual(expected, result)
|
||||
self.assertEqual(0, update_ssc.call_count)
|
||||
|
||||
def test_create_volume_no_pool(self):
|
||||
volume = copy.deepcopy(fake.NFS_VOLUME)
|
||||
volume['host'] = '%s@%s' % (fake.HOST_NAME, fake.BACKEND_NAME)
|
||||
self.mock_object(self.driver, '_ensure_shares_mounted')
|
||||
|
||||
self.assertRaises(exception.InvalidHost,
|
||||
self.driver.create_volume,
|
||||
volume)
|
||||
|
||||
def test_create_volume_exception(self):
|
||||
self.mock_object(self.driver, '_ensure_shares_mounted')
|
||||
self.mock_object(na_utils, 'get_volume_extra_specs')
|
||||
mock_create = self.mock_object(self.driver, '_do_create_volume')
|
||||
mock_create.side_effect = Exception
|
||||
update_ssc = self.mock_object(self.driver, '_update_stale_vols')
|
||||
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self.driver.create_volume,
|
||||
fake.NFS_VOLUME)
|
||||
|
||||
self.assertEqual(0, update_ssc.call_count)
|
||||
|
||||
def test_create_volume_from_snapshot(self):
|
||||
provider_location = fake.POOL_NAME
|
||||
snapshot = fake.CLONE_SOURCE
|
||||
self.mock_object(self.driver, '_clone_source_to_destination_volume',
|
||||
mock.Mock(return_value=provider_location))
|
||||
|
||||
result = self.driver.create_cloned_volume(fake.NFS_VOLUME,
|
||||
snapshot)
|
||||
|
||||
self.assertEqual(provider_location, result)
|
||||
|
||||
def test_clone_source_to_destination_volume(self):
|
||||
self.mock_object(self.driver, '_get_volume_location', mock.Mock(
|
||||
return_value=fake.POOL_NAME))
|
||||
self.mock_object(na_utils, 'get_volume_extra_specs', mock.Mock(
|
||||
return_value=fake.EXTRA_SPECS))
|
||||
self.mock_object(
|
||||
self.driver,
|
||||
'_clone_with_extension_check')
|
||||
self.mock_object(self.driver, '_do_qos_for_volume')
|
||||
expected = {'provider_location': fake.POOL_NAME}
|
||||
|
||||
result = self.driver._clone_source_to_destination_volume(
|
||||
fake.CLONE_SOURCE, fake.CLONE_DESTINATION)
|
||||
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_clone_source_to_destination_volume_with_do_qos_exception(self):
|
||||
self.mock_object(self.driver, '_get_volume_location', mock.Mock(
|
||||
return_value=fake.POOL_NAME))
|
||||
self.mock_object(na_utils, 'get_volume_extra_specs', mock.Mock(
|
||||
return_value=fake.EXTRA_SPECS))
|
||||
self.mock_object(
|
||||
self.driver,
|
||||
'_clone_with_extension_check')
|
||||
self.mock_object(self.driver, '_do_qos_for_volume', mock.Mock(
|
||||
side_effect=Exception))
|
||||
|
||||
self.assertRaises(
|
||||
exception.VolumeBackendAPIException,
|
||||
self.driver._clone_source_to_destination_volume,
|
||||
fake.CLONE_SOURCE,
|
||||
fake.CLONE_DESTINATION)
|
||||
|
||||
def test_clone_with_extension_check_equal_sizes(self):
|
||||
clone_source = copy.deepcopy(fake.CLONE_SOURCE)
|
||||
clone_source['size'] = fake.VOLUME['size']
|
||||
self.mock_object(self.driver, '_clone_backing_file_for_volume')
|
||||
self.mock_object(self.driver, 'local_path')
|
||||
mock_discover = self.mock_object(self.driver,
|
||||
'_discover_file_till_timeout')
|
||||
mock_discover.return_value = True
|
||||
self.mock_object(self.driver, '_set_rw_permissions')
|
||||
mock_extend_volume = self.mock_object(self.driver, 'extend_volume')
|
||||
|
||||
self.driver._clone_with_extension_check(clone_source, fake.NFS_VOLUME)
|
||||
|
||||
self.assertEqual(0, mock_extend_volume.call_count)
|
||||
|
||||
def test_clone_with_extension_check_unequal_sizes(self):
|
||||
clone_source = copy.deepcopy(fake.CLONE_SOURCE)
|
||||
clone_source['size'] = fake.VOLUME['size'] + 1
|
||||
self.mock_object(self.driver, '_clone_backing_file_for_volume')
|
||||
self.mock_object(self.driver, 'local_path')
|
||||
mock_discover = self.mock_object(self.driver,
|
||||
'_discover_file_till_timeout')
|
||||
mock_discover.return_value = True
|
||||
self.mock_object(self.driver, '_set_rw_permissions')
|
||||
mock_extend_volume = self.mock_object(self.driver, 'extend_volume')
|
||||
|
||||
self.driver._clone_with_extension_check(clone_source, fake.NFS_VOLUME)
|
||||
|
||||
self.assertEqual(1, mock_extend_volume.call_count)
|
||||
|
||||
def test_clone_with_extension_check_extend_exception(self):
|
||||
clone_source = copy.deepcopy(fake.CLONE_SOURCE)
|
||||
clone_source['size'] = fake.VOLUME['size'] + 1
|
||||
self.mock_object(self.driver, '_clone_backing_file_for_volume')
|
||||
self.mock_object(self.driver, 'local_path')
|
||||
mock_discover = self.mock_object(self.driver,
|
||||
'_discover_file_till_timeout')
|
||||
mock_discover.return_value = True
|
||||
self.mock_object(self.driver, '_set_rw_permissions')
|
||||
mock_extend_volume = self.mock_object(self.driver, 'extend_volume')
|
||||
mock_extend_volume.side_effect = Exception
|
||||
mock_cleanup = self.mock_object(self.driver,
|
||||
'_cleanup_volume_on_failure')
|
||||
|
||||
self.assertRaises(exception.CinderException,
|
||||
self.driver._clone_with_extension_check,
|
||||
clone_source,
|
||||
fake.NFS_VOLUME)
|
||||
|
||||
self.assertEqual(1, mock_cleanup.call_count)
|
||||
|
||||
def test_clone_with_extension_check_no_discovery(self):
|
||||
self.mock_object(self.driver, '_clone_backing_file_for_volume')
|
||||
self.mock_object(self.driver, 'local_path')
|
||||
self.mock_object(self.driver, '_set_rw_permissions')
|
||||
mock_discover = self.mock_object(self.driver,
|
||||
'_discover_file_till_timeout')
|
||||
mock_discover.return_value = False
|
||||
|
||||
self.assertRaises(exception.CinderException,
|
||||
self.driver._clone_with_extension_check,
|
||||
fake.CLONE_SOURCE,
|
||||
fake.NFS_VOLUME)
|
||||
|
||||
def test_create_cloned_volume(self):
|
||||
provider_location = fake.POOL_NAME
|
||||
src_vref = fake.CLONE_SOURCE
|
||||
self.mock_object(self.driver, '_clone_source_to_destination_volume',
|
||||
mock.Mock(return_value=provider_location))
|
||||
|
||||
result = self.driver.create_cloned_volume(fake.NFS_VOLUME,
|
||||
src_vref)
|
||||
self.assertEqual(provider_location, result)
|
||||
|
||||
def test_do_qos_for_volume(self):
|
||||
self.assertRaises(NotImplementedError,
|
||||
self.driver._do_qos_for_volume,
|
||||
fake.NFS_VOLUME,
|
||||
fake.EXTRA_SPECS)
|
||||
|
||||
def test_cleanup_volume_on_failure(self):
|
||||
path = '%s/%s' % (fake.NFS_SHARE, fake.NFS_VOLUME['name'])
|
||||
mock_local_path = self.mock_object(self.driver, 'local_path')
|
||||
mock_local_path.return_value = path
|
||||
mock_exists_check = self.mock_object(os.path, 'exists')
|
||||
mock_exists_check.return_value = True
|
||||
mock_delete = self.mock_object(self.driver, '_delete_file_at_path')
|
||||
|
||||
self.driver._cleanup_volume_on_failure(fake.NFS_VOLUME)
|
||||
|
||||
mock_delete.assert_has_calls([mock.call(path)])
|
||||
|
||||
def test_cleanup_volume_on_failure_no_path(self):
|
||||
self.mock_object(self.driver, 'local_path')
|
||||
mock_exists_check = self.mock_object(os.path, 'exists')
|
||||
mock_exists_check.return_value = False
|
||||
mock_delete = self.mock_object(self.driver, '_delete_file_at_path')
|
||||
|
||||
self.driver._cleanup_volume_on_failure(fake.NFS_VOLUME)
|
||||
|
||||
self.assertEqual(0, mock_delete.call_count)
|
||||
|
||||
def test_get_vol_for_share(self):
|
||||
self.assertRaises(NotImplementedError,
|
||||
self.driver._get_vol_for_share,
|
||||
fake.NFS_SHARE)
|
||||
|
|
|
@ -18,16 +18,26 @@ Mock unit tests for the NetApp cmode nfs storage driver
|
|||
|
||||
import mock
|
||||
from os_brick.remotefs import remotefs as remotefs_brick
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import units
|
||||
|
||||
from cinder import exception
|
||||
from cinder.openstack.common import loopingcall
|
||||
from cinder import test
|
||||
from cinder.tests.unit.volume.drivers.netapp.dataontap import fakes as fake
|
||||
from cinder.tests.unit.volume.drivers.netapp import fakes as na_fakes
|
||||
from cinder import utils
|
||||
from cinder.volume.drivers.netapp.dataontap.client import api as netapp_api
|
||||
from cinder.volume.drivers.netapp.dataontap.client import client_cmode
|
||||
from cinder.volume.drivers.netapp.dataontap import nfs_base
|
||||
from cinder.volume.drivers.netapp.dataontap import nfs_cmode
|
||||
from cinder.volume.drivers.netapp.dataontap import ssc_cmode
|
||||
from cinder.volume.drivers.netapp import utils as na_utils
|
||||
from cinder.volume.drivers import nfs
|
||||
from cinder.volume import utils as volume_utils
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
||||
|
@ -43,6 +53,8 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
|||
self.driver = nfs_cmode.NetAppCmodeNfsDriver(**kwargs)
|
||||
self.driver._mounted_shares = [fake.NFS_SHARE]
|
||||
self.driver.ssc_vols = True
|
||||
self.driver.vserver = fake.VSERVER_NAME
|
||||
self.driver.ssc_enabled = True
|
||||
|
||||
def get_config_cmode(self):
|
||||
config = na_fakes.create_configuration_cmode()
|
||||
|
@ -52,7 +64,7 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
|||
config.netapp_server_hostname = '127.0.0.1'
|
||||
config.netapp_transport_type = 'http'
|
||||
config.netapp_server_port = '80'
|
||||
config.netapp_vserver = 'openstack'
|
||||
config.netapp_vserver = fake.VSERVER_NAME
|
||||
return config
|
||||
|
||||
@mock.patch.object(client_cmode, 'Client', mock.Mock())
|
||||
|
@ -90,3 +102,286 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
|||
result[0]['reserved_percentage'])
|
||||
self.assertEqual(total_capacity_gb, result[0]['total_capacity_gb'])
|
||||
self.assertEqual(free_capacity_gb, result[0]['free_capacity_gb'])
|
||||
|
||||
def test_check_for_setup_error(self):
|
||||
super_check_for_setup_error = self.mock_object(
|
||||
nfs_base.NetAppNfsDriver, 'check_for_setup_error')
|
||||
mock_check_ssc_api_permissions = self.mock_object(
|
||||
ssc_cmode, 'check_ssc_api_permissions')
|
||||
mock_start_periodic_tasks = self.mock_object(
|
||||
self.driver, '_start_periodic_tasks')
|
||||
self.driver.zapi_client = mock.Mock()
|
||||
|
||||
self.driver.check_for_setup_error()
|
||||
|
||||
self.assertEqual(1, super_check_for_setup_error.call_count)
|
||||
mock_check_ssc_api_permissions.assert_called_once_with(
|
||||
self.driver.zapi_client)
|
||||
self.assertEqual(1, mock_start_periodic_tasks.call_count)
|
||||
|
||||
def test_delete_volume(self):
|
||||
fake_provider_location = 'fake_provider_location'
|
||||
fake_volume = {'name': 'fake_name',
|
||||
'provider_location': 'fake_provider_location'}
|
||||
fake_qos_policy_group_info = {'legacy': None, 'spec': None}
|
||||
self.mock_object(nfs_base.NetAppNfsDriver, 'delete_volume')
|
||||
self.mock_object(na_utils, 'get_valid_qos_policy_group_info',
|
||||
mock.Mock(return_value=fake_qos_policy_group_info))
|
||||
self.mock_object(self.driver, '_post_prov_deprov_in_ssc')
|
||||
self.driver.zapi_client = mock.Mock()
|
||||
|
||||
self.driver.delete_volume(fake_volume)
|
||||
|
||||
nfs_base.NetAppNfsDriver.delete_volume.assert_called_once_with(
|
||||
fake_volume)
|
||||
self.driver.zapi_client.mark_qos_policy_group_for_deletion\
|
||||
.assert_called_once_with(fake_qos_policy_group_info)
|
||||
self.driver._post_prov_deprov_in_ssc.assert_called_once_with(
|
||||
fake_provider_location)
|
||||
|
||||
def test_delete_volume_get_qos_info_exception(self):
|
||||
fake_provider_location = 'fake_provider_location'
|
||||
fake_volume = {'name': 'fake_name',
|
||||
'provider_location': 'fake_provider_location'}
|
||||
self.mock_object(nfs_base.NetAppNfsDriver, 'delete_volume')
|
||||
self.mock_object(na_utils, 'get_valid_qos_policy_group_info',
|
||||
mock.Mock(side_effect=exception.Invalid))
|
||||
self.mock_object(self.driver, '_post_prov_deprov_in_ssc')
|
||||
|
||||
self.driver.delete_volume(fake_volume)
|
||||
|
||||
nfs_base.NetAppNfsDriver.delete_volume.assert_called_once_with(
|
||||
fake_volume)
|
||||
self.driver._post_prov_deprov_in_ssc.assert_called_once_with(
|
||||
fake_provider_location)
|
||||
|
||||
def test_do_qos_for_volume_no_exception(self):
|
||||
|
||||
mock_get_info = self.mock_object(na_utils,
|
||||
'get_valid_qos_policy_group_info')
|
||||
mock_get_info.return_value = fake.QOS_POLICY_GROUP_INFO
|
||||
self.driver.zapi_client = mock.Mock()
|
||||
mock_provision_qos = self.driver.zapi_client.provision_qos_policy_group
|
||||
mock_set_policy = self.mock_object(self.driver,
|
||||
'_set_qos_policy_group_on_volume')
|
||||
mock_error_log = self.mock_object(nfs_cmode.LOG, 'error')
|
||||
mock_debug_log = self.mock_object(nfs_cmode.LOG, 'debug')
|
||||
mock_cleanup = self.mock_object(self.driver,
|
||||
'_cleanup_volume_on_failure')
|
||||
|
||||
self.driver._do_qos_for_volume(fake.NFS_VOLUME, fake.EXTRA_SPECS)
|
||||
|
||||
mock_get_info.assert_has_calls([
|
||||
mock.call(fake.NFS_VOLUME, fake.EXTRA_SPECS)])
|
||||
mock_provision_qos.assert_has_calls([
|
||||
mock.call(fake.QOS_POLICY_GROUP_INFO)])
|
||||
mock_set_policy.assert_has_calls([
|
||||
mock.call(fake.NFS_VOLUME, fake.QOS_POLICY_GROUP_INFO)])
|
||||
self.assertEqual(0, mock_error_log.call_count)
|
||||
self.assertEqual(0, mock_debug_log.call_count)
|
||||
self.assertEqual(0, mock_cleanup.call_count)
|
||||
|
||||
def test_do_qos_for_volume_exception_w_cleanup(self):
|
||||
mock_get_info = self.mock_object(na_utils,
|
||||
'get_valid_qos_policy_group_info')
|
||||
mock_get_info.return_value = fake.QOS_POLICY_GROUP_INFO
|
||||
self.driver.zapi_client = mock.Mock()
|
||||
mock_provision_qos = self.driver.zapi_client.provision_qos_policy_group
|
||||
mock_set_policy = self.mock_object(self.driver,
|
||||
'_set_qos_policy_group_on_volume')
|
||||
mock_set_policy.side_effect = netapp_api.NaApiError
|
||||
mock_error_log = self.mock_object(nfs_cmode.LOG, 'error')
|
||||
mock_debug_log = self.mock_object(nfs_cmode.LOG, 'debug')
|
||||
mock_cleanup = self.mock_object(self.driver,
|
||||
'_cleanup_volume_on_failure')
|
||||
|
||||
self.assertRaises(netapp_api.NaApiError,
|
||||
self.driver._do_qos_for_volume,
|
||||
fake.NFS_VOLUME,
|
||||
fake.EXTRA_SPECS)
|
||||
|
||||
mock_get_info.assert_has_calls([
|
||||
mock.call(fake.NFS_VOLUME, fake.EXTRA_SPECS)])
|
||||
mock_provision_qos.assert_has_calls([
|
||||
mock.call(fake.QOS_POLICY_GROUP_INFO)])
|
||||
mock_set_policy.assert_has_calls([
|
||||
mock.call(fake.NFS_VOLUME, fake.QOS_POLICY_GROUP_INFO)])
|
||||
self.assertEqual(1, mock_error_log.call_count)
|
||||
self.assertEqual(1, mock_debug_log.call_count)
|
||||
mock_cleanup.assert_has_calls([
|
||||
mock.call(fake.NFS_VOLUME)])
|
||||
|
||||
def test_do_qos_for_volume_exception_no_cleanup(self):
|
||||
|
||||
mock_get_info = self.mock_object(na_utils,
|
||||
'get_valid_qos_policy_group_info')
|
||||
mock_get_info.side_effect = exception.Invalid
|
||||
self.driver.zapi_client = mock.Mock()
|
||||
mock_provision_qos = self.driver.zapi_client.provision_qos_policy_group
|
||||
mock_set_policy = self.mock_object(self.driver,
|
||||
'_set_qos_policy_group_on_volume')
|
||||
mock_error_log = self.mock_object(nfs_cmode.LOG, 'error')
|
||||
mock_debug_log = self.mock_object(nfs_cmode.LOG, 'debug')
|
||||
mock_cleanup = self.mock_object(self.driver,
|
||||
'_cleanup_volume_on_failure')
|
||||
|
||||
self.assertRaises(exception.Invalid, self.driver._do_qos_for_volume,
|
||||
fake.NFS_VOLUME, fake.EXTRA_SPECS, cleanup=False)
|
||||
|
||||
mock_get_info.assert_has_calls([
|
||||
mock.call(fake.NFS_VOLUME, fake.EXTRA_SPECS)])
|
||||
self.assertEqual(0, mock_provision_qos.call_count)
|
||||
self.assertEqual(0, mock_set_policy.call_count)
|
||||
self.assertEqual(1, mock_error_log.call_count)
|
||||
self.assertEqual(0, mock_debug_log.call_count)
|
||||
self.assertEqual(0, mock_cleanup.call_count)
|
||||
|
||||
def test_set_qos_policy_group_on_volume(self):
|
||||
|
||||
mock_get_name_from_info = self.mock_object(
|
||||
na_utils, 'get_qos_policy_group_name_from_info')
|
||||
mock_get_name_from_info.return_value = fake.QOS_POLICY_GROUP_NAME
|
||||
|
||||
mock_extract_host = self.mock_object(volume_utils, 'extract_host')
|
||||
mock_extract_host.return_value = fake.NFS_SHARE
|
||||
|
||||
self.driver.zapi_client = mock.Mock()
|
||||
mock_get_flex_vol_name =\
|
||||
self.driver.zapi_client.get_vol_by_junc_vserver
|
||||
mock_get_flex_vol_name.return_value = fake.FLEXVOL
|
||||
|
||||
mock_file_assign_qos = self.driver.zapi_client.file_assign_qos
|
||||
|
||||
self.driver._set_qos_policy_group_on_volume(fake.NFS_VOLUME,
|
||||
fake.QOS_POLICY_GROUP_INFO)
|
||||
|
||||
mock_get_name_from_info.assert_has_calls([
|
||||
mock.call(fake.QOS_POLICY_GROUP_INFO)])
|
||||
mock_extract_host.assert_has_calls([
|
||||
mock.call(fake.NFS_HOST_STRING, level='pool')])
|
||||
mock_get_flex_vol_name.assert_has_calls([
|
||||
mock.call(fake.VSERVER_NAME, fake.EXPORT_PATH)])
|
||||
mock_file_assign_qos.assert_has_calls([
|
||||
mock.call(fake.FLEXVOL, fake.QOS_POLICY_GROUP_NAME,
|
||||
fake.NFS_VOLUME['name'])])
|
||||
|
||||
def test_set_qos_policy_group_on_volume_no_info(self):
|
||||
|
||||
mock_get_name_from_info = self.mock_object(
|
||||
na_utils, 'get_qos_policy_group_name_from_info')
|
||||
|
||||
mock_extract_host = self.mock_object(volume_utils, 'extract_host')
|
||||
|
||||
self.driver.zapi_client = mock.Mock()
|
||||
mock_get_flex_vol_name =\
|
||||
self.driver.zapi_client.get_vol_by_junc_vserver
|
||||
|
||||
mock_file_assign_qos = self.driver.zapi_client.file_assign_qos
|
||||
|
||||
self.driver._set_qos_policy_group_on_volume(fake.NFS_VOLUME,
|
||||
None)
|
||||
|
||||
self.assertEqual(0, mock_get_name_from_info.call_count)
|
||||
self.assertEqual(0, mock_extract_host.call_count)
|
||||
self.assertEqual(0, mock_get_flex_vol_name.call_count)
|
||||
self.assertEqual(0, mock_file_assign_qos.call_count)
|
||||
|
||||
def test_set_qos_policy_group_on_volume_no_name(self):
|
||||
|
||||
mock_get_name_from_info = self.mock_object(
|
||||
na_utils, 'get_qos_policy_group_name_from_info')
|
||||
mock_get_name_from_info.return_value = None
|
||||
|
||||
mock_extract_host = self.mock_object(volume_utils, 'extract_host')
|
||||
|
||||
self.driver.zapi_client = mock.Mock()
|
||||
mock_get_flex_vol_name =\
|
||||
self.driver.zapi_client.get_vol_by_junc_vserver
|
||||
|
||||
mock_file_assign_qos = self.driver.zapi_client.file_assign_qos
|
||||
|
||||
self.driver._set_qos_policy_group_on_volume(fake.NFS_VOLUME,
|
||||
fake.QOS_POLICY_GROUP_INFO)
|
||||
|
||||
mock_get_name_from_info.assert_has_calls([
|
||||
mock.call(fake.QOS_POLICY_GROUP_INFO)])
|
||||
self.assertEqual(0, mock_extract_host.call_count)
|
||||
self.assertEqual(0, mock_get_flex_vol_name.call_count)
|
||||
self.assertEqual(0, mock_file_assign_qos.call_count)
|
||||
|
||||
def test_unmanage(self):
|
||||
mock_get_info = self.mock_object(na_utils,
|
||||
'get_valid_qos_policy_group_info')
|
||||
mock_get_info.return_value = fake.QOS_POLICY_GROUP_INFO
|
||||
|
||||
self.driver.zapi_client = mock.Mock()
|
||||
mock_mark_for_deletion =\
|
||||
self.driver.zapi_client.mark_qos_policy_group_for_deletion
|
||||
|
||||
super_unmanage = self.mock_object(nfs_base.NetAppNfsDriver, 'unmanage')
|
||||
|
||||
self.driver.unmanage(fake.NFS_VOLUME)
|
||||
|
||||
mock_get_info.assert_has_calls([mock.call(fake.NFS_VOLUME)])
|
||||
mock_mark_for_deletion.assert_has_calls([
|
||||
mock.call(fake.QOS_POLICY_GROUP_INFO)])
|
||||
super_unmanage.assert_has_calls([mock.call(fake.NFS_VOLUME)])
|
||||
|
||||
def test_unmanage_invalid_qos(self):
|
||||
mock_get_info = self.mock_object(na_utils,
|
||||
'get_valid_qos_policy_group_info')
|
||||
mock_get_info.side_effect = exception.Invalid
|
||||
|
||||
super_unmanage = self.mock_object(nfs_base.NetAppNfsDriver, 'unmanage')
|
||||
|
||||
self.driver.unmanage(fake.NFS_VOLUME)
|
||||
|
||||
mock_get_info.assert_has_calls([mock.call(fake.NFS_VOLUME)])
|
||||
super_unmanage.assert_has_calls([mock.call(fake.NFS_VOLUME)])
|
||||
|
||||
def test_create_volume(self):
|
||||
self.mock_object(self.driver, '_ensure_shares_mounted')
|
||||
self.mock_object(na_utils, 'get_volume_extra_specs')
|
||||
self.mock_object(self.driver, '_do_create_volume')
|
||||
self.mock_object(self.driver, '_do_qos_for_volume')
|
||||
update_ssc = self.mock_object(self.driver, '_update_stale_vols')
|
||||
self.mock_object(self.driver, '_get_vol_for_share')
|
||||
expected = {'provider_location': fake.NFS_SHARE}
|
||||
|
||||
result = self.driver.create_volume(fake.NFS_VOLUME)
|
||||
|
||||
self.assertEqual(expected, result)
|
||||
self.assertEqual(1, update_ssc.call_count)
|
||||
|
||||
def test_create_volume_exception(self):
|
||||
self.mock_object(self.driver, '_ensure_shares_mounted')
|
||||
self.mock_object(na_utils, 'get_volume_extra_specs')
|
||||
mock_create = self.mock_object(self.driver, '_do_create_volume')
|
||||
mock_create.side_effect = Exception
|
||||
update_ssc = self.mock_object(self.driver, '_update_stale_vols')
|
||||
self.mock_object(self.driver, '_get_vol_for_share')
|
||||
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self.driver.create_volume,
|
||||
fake.NFS_VOLUME)
|
||||
|
||||
self.assertEqual(1, update_ssc.call_count)
|
||||
|
||||
def test_start_periodic_tasks(self):
|
||||
|
||||
self.driver.zapi_client = mock.Mock()
|
||||
mock_remove_unused_qos_policy_groups = self.mock_object(
|
||||
self.driver.zapi_client,
|
||||
'remove_unused_qos_policy_groups')
|
||||
|
||||
harvest_qos_periodic_task = mock.Mock()
|
||||
mock_loopingcall = self.mock_object(
|
||||
loopingcall,
|
||||
'FixedIntervalLoopingCall',
|
||||
mock.Mock(side_effect=[harvest_qos_periodic_task]))
|
||||
|
||||
self.driver._start_periodic_tasks()
|
||||
|
||||
mock_loopingcall.assert_has_calls([
|
||||
mock.call(mock_remove_unused_qos_policy_groups)])
|
||||
self.assertTrue(harvest_qos_periodic_task.start.called)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# Copyright (c) - 2014, Clinton Knight All rights reserved.
|
||||
# Copyright (c) - 2015, Alex Meade. All Rights Reserved.
|
||||
# Copyright (c) - 2015, Rushil Chugh. All Rights Reserved.
|
||||
# Copyright (c) - 2015, Tom Barron. 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
|
||||
|
@ -43,6 +44,69 @@ FC_ISCSI_TARGET_INFO_DICT = {'target_discovered': False,
|
|||
'auth_method': 'None', 'auth_username': 'stack',
|
||||
'auth_password': 'password'}
|
||||
|
||||
VOLUME_NAME = 'fake_volume_name'
|
||||
VOLUME_ID = 'fake_volume_id'
|
||||
VOLUME_TYPE_ID = 'fake_volume_type_id'
|
||||
|
||||
VOLUME = {
|
||||
'name': VOLUME_NAME,
|
||||
'size': 42,
|
||||
'id': VOLUME_ID,
|
||||
'host': 'fake_host@fake_backend#fake_pool',
|
||||
'volume_type_id': VOLUME_TYPE_ID,
|
||||
}
|
||||
|
||||
|
||||
QOS_SPECS = {}
|
||||
|
||||
EXTRA_SPECS = {}
|
||||
|
||||
MAX_THROUGHPUT = '21734278B/s'
|
||||
QOS_POLICY_GROUP_NAME = 'fake_qos_policy_group_name'
|
||||
LEGACY_EXTRA_SPECS = {'netapp:qos_policy_group': QOS_POLICY_GROUP_NAME}
|
||||
|
||||
LEGACY_QOS = {
|
||||
'policy_name': QOS_POLICY_GROUP_NAME,
|
||||
}
|
||||
|
||||
QOS_POLICY_GROUP_SPEC = {
|
||||
'max_throughput': MAX_THROUGHPUT,
|
||||
'policy_name': 'openstack-%s' % VOLUME_ID,
|
||||
}
|
||||
|
||||
QOS_POLICY_GROUP_INFO_NONE = {'legacy': None, 'spec': None}
|
||||
|
||||
QOS_POLICY_GROUP_INFO = {'legacy': None, 'spec': QOS_POLICY_GROUP_SPEC}
|
||||
|
||||
LEGACY_QOS_POLICY_GROUP_INFO = {
|
||||
'legacy': LEGACY_QOS,
|
||||
'spec': None,
|
||||
}
|
||||
|
||||
INVALID_QOS_POLICY_GROUP_INFO = {
|
||||
'legacy': LEGACY_QOS,
|
||||
'spec': QOS_POLICY_GROUP_SPEC,
|
||||
}
|
||||
|
||||
QOS_SPECS_ID = 'fake_qos_specs_id'
|
||||
QOS_SPEC = {'maxBPS': 21734278}
|
||||
OUTER_BACKEND_QOS_SPEC = {
|
||||
'id': QOS_SPECS_ID,
|
||||
'specs': QOS_SPEC,
|
||||
'consumer': 'back-end',
|
||||
}
|
||||
OUTER_FRONTEND_QOS_SPEC = {
|
||||
'id': QOS_SPECS_ID,
|
||||
'specs': QOS_SPEC,
|
||||
'consumer': 'front-end',
|
||||
}
|
||||
OUTER_BOTH_QOS_SPEC = {
|
||||
'id': QOS_SPECS_ID,
|
||||
'specs': QOS_SPEC,
|
||||
'consumer': 'both',
|
||||
}
|
||||
VOLUME_TYPE = {'id': VOLUME_TYPE_ID, 'qos_specs_id': QOS_SPECS_ID}
|
||||
|
||||
|
||||
def create_configuration():
|
||||
config = conf.Configuration(None)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Copyright (c) 2014 Clinton Knight. All rights reserved.
|
||||
# Copyright (c) 2014 Tom Barron. All rights reserved.
|
||||
# Copyright (c) 2015 Tom Barron. All rights reserved.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
|
@ -17,16 +17,20 @@
|
|||
Mock unit tests for the NetApp driver utility module
|
||||
"""
|
||||
|
||||
import copy
|
||||
import platform
|
||||
|
||||
import mock
|
||||
from oslo_concurrency import processutils as putils
|
||||
|
||||
from cinder import context
|
||||
from cinder import exception
|
||||
from cinder import test
|
||||
import cinder.tests.unit.volume.drivers.netapp.fakes as fake
|
||||
from cinder import version
|
||||
import cinder.volume.drivers.netapp.utils as na_utils
|
||||
from cinder.volume.drivers.netapp import utils as na_utils
|
||||
from cinder.volume import qos_specs
|
||||
from cinder.volume import volume_types
|
||||
|
||||
|
||||
class NetAppDriverUtilsTestCase(test.TestCase):
|
||||
|
@ -106,6 +110,386 @@ class NetAppDriverUtilsTestCase(test.TestCase):
|
|||
self.assertAlmostEqual(na_utils.round_down(-5.567, '0.0'), -5.5)
|
||||
self.assertAlmostEqual(na_utils.round_down(-5.567, '0'), -5)
|
||||
|
||||
def test_iscsi_connection_properties(self):
|
||||
|
||||
actual_properties = na_utils.get_iscsi_connection_properties(
|
||||
fake.ISCSI_FAKE_LUN_ID, fake.ISCSI_FAKE_VOLUME,
|
||||
fake.ISCSI_FAKE_IQN, fake.ISCSI_FAKE_ADDRESS,
|
||||
fake.ISCSI_FAKE_PORT)
|
||||
|
||||
actual_properties_mapped = actual_properties['data']
|
||||
|
||||
self.assertDictEqual(actual_properties_mapped,
|
||||
fake.FC_ISCSI_TARGET_INFO_DICT)
|
||||
|
||||
def test_iscsi_connection_lun_id_type_str(self):
|
||||
FAKE_LUN_ID = '1'
|
||||
|
||||
actual_properties = na_utils.get_iscsi_connection_properties(
|
||||
FAKE_LUN_ID, fake.ISCSI_FAKE_VOLUME, fake.ISCSI_FAKE_IQN,
|
||||
fake.ISCSI_FAKE_ADDRESS, fake.ISCSI_FAKE_PORT)
|
||||
|
||||
actual_properties_mapped = actual_properties['data']
|
||||
|
||||
self.assertIs(type(actual_properties_mapped['target_lun']), int)
|
||||
|
||||
def test_iscsi_connection_lun_id_type_dict(self):
|
||||
FAKE_LUN_ID = {'id': 'fake_id'}
|
||||
|
||||
self.assertRaises(TypeError, na_utils.get_iscsi_connection_properties,
|
||||
FAKE_LUN_ID, fake.ISCSI_FAKE_VOLUME,
|
||||
fake.ISCSI_FAKE_IQN, fake.ISCSI_FAKE_ADDRESS,
|
||||
fake.ISCSI_FAKE_PORT)
|
||||
|
||||
def test_get_volume_extra_specs(self):
|
||||
fake_extra_specs = {'fake_key': 'fake_value'}
|
||||
fake_volume_type = {'extra_specs': fake_extra_specs}
|
||||
fake_volume = {'volume_type_id': 'fake_volume_type_id'}
|
||||
self.mock_object(context, 'get_admin_context')
|
||||
self.mock_object(volume_types, 'get_volume_type', mock.Mock(
|
||||
return_value=fake_volume_type))
|
||||
self.mock_object(na_utils, 'log_extra_spec_warnings')
|
||||
|
||||
result = na_utils.get_volume_extra_specs(fake_volume)
|
||||
|
||||
self.assertEqual(fake_extra_specs, result)
|
||||
|
||||
def test_get_volume_extra_specs_no_type_id(self):
|
||||
fake_volume = {}
|
||||
self.mock_object(context, 'get_admin_context')
|
||||
self.mock_object(volume_types, 'get_volume_type')
|
||||
self.mock_object(na_utils, 'log_extra_spec_warnings')
|
||||
|
||||
result = na_utils.get_volume_extra_specs(fake_volume)
|
||||
|
||||
self.assertEqual({}, result)
|
||||
|
||||
def test_get_volume_extra_specs_no_volume_type(self):
|
||||
fake_volume = {'volume_type_id': 'fake_volume_type_id'}
|
||||
self.mock_object(context, 'get_admin_context')
|
||||
self.mock_object(volume_types, 'get_volume_type', mock.Mock(
|
||||
return_value=None))
|
||||
self.mock_object(na_utils, 'log_extra_spec_warnings')
|
||||
|
||||
result = na_utils.get_volume_extra_specs(fake_volume)
|
||||
|
||||
self.assertEqual({}, result)
|
||||
|
||||
def test_log_extra_spec_warnings_obsolete_specs(self):
|
||||
|
||||
mock_log = self.mock_object(na_utils.LOG, 'warning')
|
||||
|
||||
na_utils.log_extra_spec_warnings({'netapp:raid_type': 'raid4'})
|
||||
|
||||
self.assertEqual(1, mock_log.call_count)
|
||||
|
||||
def test_log_extra_spec_warnings_deprecated_specs(self):
|
||||
|
||||
mock_log = self.mock_object(na_utils.LOG, 'warning')
|
||||
|
||||
na_utils.log_extra_spec_warnings({'netapp_thick_provisioned': 'true'})
|
||||
|
||||
self.assertEqual(1, mock_log.call_count)
|
||||
|
||||
def test_validate_qos_spec_none(self):
|
||||
qos_spec = None
|
||||
|
||||
# Just return without raising an exception.
|
||||
na_utils.validate_qos_spec(qos_spec)
|
||||
|
||||
def test_validate_qos_spec_keys_weirdly_cased(self):
|
||||
qos_spec = {'mAxIopS': 33000}
|
||||
|
||||
# Just return without raising an exception.
|
||||
na_utils.validate_qos_spec(qos_spec)
|
||||
|
||||
def test_validate_qos_spec_bad_key(self):
|
||||
qos_spec = {'maxFlops': 33000}
|
||||
|
||||
self.assertRaises(exception.Invalid,
|
||||
na_utils.validate_qos_spec,
|
||||
qos_spec)
|
||||
|
||||
def test_validate_qos_spec_bad_key_combination(self):
|
||||
qos_spec = {'maxIOPS': 33000, 'maxBPS': 10000000}
|
||||
|
||||
self.assertRaises(exception.Invalid,
|
||||
na_utils.validate_qos_spec,
|
||||
qos_spec)
|
||||
|
||||
def test_map_qos_spec_none(self):
|
||||
qos_spec = None
|
||||
|
||||
result = na_utils.map_qos_spec(qos_spec, fake.VOLUME)
|
||||
|
||||
self.assertEqual(None, result)
|
||||
|
||||
def test_map_qos_spec_maxiops(self):
|
||||
qos_spec = {'maxIOPs': 33000}
|
||||
mock_get_name = self.mock_object(na_utils, 'get_qos_policy_group_name')
|
||||
mock_get_name.return_value = 'fake_qos_policy'
|
||||
expected = {
|
||||
'policy_name': 'fake_qos_policy',
|
||||
'max_throughput': '33000iops',
|
||||
}
|
||||
|
||||
result = na_utils.map_qos_spec(qos_spec, fake.VOLUME)
|
||||
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_map_qos_spec_maxbps(self):
|
||||
qos_spec = {'maxBPS': 1000000}
|
||||
mock_get_name = self.mock_object(na_utils, 'get_qos_policy_group_name')
|
||||
mock_get_name.return_value = 'fake_qos_policy'
|
||||
expected = {
|
||||
'policy_name': 'fake_qos_policy',
|
||||
'max_throughput': '1000000B/s',
|
||||
}
|
||||
|
||||
result = na_utils.map_qos_spec(qos_spec, fake.VOLUME)
|
||||
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_map_qos_spec_no_key_present(self):
|
||||
qos_spec = {}
|
||||
mock_get_name = self.mock_object(na_utils, 'get_qos_policy_group_name')
|
||||
mock_get_name.return_value = 'fake_qos_policy'
|
||||
expected = {
|
||||
'policy_name': 'fake_qos_policy',
|
||||
'max_throughput': None,
|
||||
}
|
||||
|
||||
result = na_utils.map_qos_spec(qos_spec, fake.VOLUME)
|
||||
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_map_dict_to_lower(self):
|
||||
original = {'UPperKey': 'Value'}
|
||||
expected = {'upperkey': 'Value'}
|
||||
|
||||
result = na_utils.map_dict_to_lower(original)
|
||||
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_get_qos_policy_group_name(self):
|
||||
expected = 'openstack-%s' % fake.VOLUME_ID
|
||||
|
||||
result = na_utils.get_qos_policy_group_name(fake.VOLUME)
|
||||
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_get_qos_policy_group_name_no_id(self):
|
||||
volume = copy.deepcopy(fake.VOLUME)
|
||||
del(volume['id'])
|
||||
|
||||
result = na_utils.get_qos_policy_group_name(volume)
|
||||
|
||||
self.assertEqual(None, result)
|
||||
|
||||
def test_get_qos_policy_group_name_from_info(self):
|
||||
expected = 'openstack-%s' % fake.VOLUME_ID
|
||||
result = na_utils.get_qos_policy_group_name_from_info(
|
||||
fake.QOS_POLICY_GROUP_INFO)
|
||||
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_get_qos_policy_group_name_from_info_no_info(self):
|
||||
|
||||
result = na_utils.get_qos_policy_group_name_from_info(None)
|
||||
|
||||
self.assertEqual(None, result)
|
||||
|
||||
def test_get_qos_policy_group_name_from_legacy_info(self):
|
||||
expected = fake.QOS_POLICY_GROUP_NAME
|
||||
|
||||
result = na_utils.get_qos_policy_group_name_from_info(
|
||||
fake.LEGACY_QOS_POLICY_GROUP_INFO)
|
||||
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_get_qos_policy_group_name_from_spec_info(self):
|
||||
expected = 'openstack-%s' % fake.VOLUME_ID
|
||||
|
||||
result = na_utils.get_qos_policy_group_name_from_info(
|
||||
fake.QOS_POLICY_GROUP_INFO)
|
||||
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_get_qos_policy_group_name_from_none_qos_info(self):
|
||||
expected = None
|
||||
|
||||
result = na_utils.get_qos_policy_group_name_from_info(
|
||||
fake.QOS_POLICY_GROUP_INFO_NONE)
|
||||
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_get_valid_qos_policy_group_info_exception_path(self):
|
||||
mock_get_volume_type = self.mock_object(na_utils,
|
||||
'get_volume_type_from_volume')
|
||||
mock_get_volume_type.side_effect = exception.VolumeTypeNotFound
|
||||
expected = fake.QOS_POLICY_GROUP_INFO_NONE
|
||||
|
||||
result = na_utils.get_valid_qos_policy_group_info(fake.VOLUME)
|
||||
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_get_valid_qos_policy_group_info_volume_type_none(self):
|
||||
mock_get_volume_type = self.mock_object(na_utils,
|
||||
'get_volume_type_from_volume')
|
||||
mock_get_volume_type.return_value = None
|
||||
expected = fake.QOS_POLICY_GROUP_INFO_NONE
|
||||
|
||||
result = na_utils.get_valid_qos_policy_group_info(fake.VOLUME)
|
||||
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_get_valid_qos_policy_group_info_no_info(self):
|
||||
mock_get_volume_type = self.mock_object(na_utils,
|
||||
'get_volume_type_from_volume')
|
||||
mock_get_volume_type.return_value = fake.VOLUME_TYPE
|
||||
mock_get_legacy_qos_policy = self.mock_object(na_utils,
|
||||
'get_legacy_qos_policy')
|
||||
mock_get_legacy_qos_policy.return_value = None
|
||||
mock_get_valid_qos_spec_from_volume_type = self.mock_object(
|
||||
na_utils, 'get_valid_backend_qos_spec_from_volume_type')
|
||||
mock_get_valid_qos_spec_from_volume_type.return_value = None
|
||||
self.mock_object(na_utils, 'check_for_invalid_qos_spec_combination')
|
||||
expected = fake.QOS_POLICY_GROUP_INFO_NONE
|
||||
|
||||
result = na_utils.get_valid_qos_policy_group_info(fake.VOLUME)
|
||||
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_get_valid_legacy_qos_policy_group_info(self):
|
||||
mock_get_volume_type = self.mock_object(na_utils,
|
||||
'get_volume_type_from_volume')
|
||||
mock_get_volume_type.return_value = fake.VOLUME_TYPE
|
||||
mock_get_legacy_qos_policy = self.mock_object(na_utils,
|
||||
'get_legacy_qos_policy')
|
||||
|
||||
mock_get_legacy_qos_policy.return_value = fake.LEGACY_QOS
|
||||
mock_get_valid_qos_spec_from_volume_type = self.mock_object(
|
||||
na_utils, 'get_valid_backend_qos_spec_from_volume_type')
|
||||
mock_get_valid_qos_spec_from_volume_type.return_value = None
|
||||
self.mock_object(na_utils, 'check_for_invalid_qos_spec_combination')
|
||||
|
||||
result = na_utils.get_valid_qos_policy_group_info(fake.VOLUME)
|
||||
|
||||
self.assertEqual(fake.LEGACY_QOS_POLICY_GROUP_INFO, result)
|
||||
|
||||
def test_get_valid_spec_qos_policy_group_info(self):
|
||||
mock_get_volume_type = self.mock_object(na_utils,
|
||||
'get_volume_type_from_volume')
|
||||
mock_get_volume_type.return_value = fake.VOLUME_TYPE
|
||||
mock_get_legacy_qos_policy = self.mock_object(na_utils,
|
||||
'get_legacy_qos_policy')
|
||||
mock_get_legacy_qos_policy.return_value = None
|
||||
mock_get_valid_qos_spec_from_volume_type = self.mock_object(
|
||||
na_utils, 'get_valid_backend_qos_spec_from_volume_type')
|
||||
mock_get_valid_qos_spec_from_volume_type.return_value =\
|
||||
fake.QOS_POLICY_GROUP_SPEC
|
||||
self.mock_object(na_utils, 'check_for_invalid_qos_spec_combination')
|
||||
|
||||
result = na_utils.get_valid_qos_policy_group_info(fake.VOLUME)
|
||||
|
||||
self.assertEqual(fake.QOS_POLICY_GROUP_INFO, result)
|
||||
|
||||
def test_get_valid_backend_qos_spec_from_volume_type_no_spec(self):
|
||||
mock_get_spec = self.mock_object(
|
||||
na_utils, 'get_backend_qos_spec_from_volume_type')
|
||||
mock_get_spec.return_value = None
|
||||
mock_validate = self.mock_object(na_utils, 'validate_qos_spec')
|
||||
|
||||
result = na_utils.get_valid_backend_qos_spec_from_volume_type(
|
||||
fake.VOLUME, fake.VOLUME_TYPE)
|
||||
|
||||
self.assertEqual(None, result)
|
||||
self.assertEqual(0, mock_validate.call_count)
|
||||
|
||||
def test_get_valid_backend_qos_spec_from_volume_type(self):
|
||||
mock_get_spec = self.mock_object(
|
||||
na_utils, 'get_backend_qos_spec_from_volume_type')
|
||||
mock_get_spec.return_value = fake.QOS_SPEC
|
||||
mock_validate = self.mock_object(na_utils, 'validate_qos_spec')
|
||||
|
||||
result = na_utils.get_valid_backend_qos_spec_from_volume_type(
|
||||
fake.VOLUME, fake.VOLUME_TYPE)
|
||||
|
||||
self.assertEqual(fake.QOS_POLICY_GROUP_SPEC, result)
|
||||
self.assertEqual(1, mock_validate.call_count)
|
||||
|
||||
def test_get_backend_qos_spec_from_volume_type_no_qos_specs_id(self):
|
||||
volume_type = copy.deepcopy(fake.VOLUME_TYPE)
|
||||
del(volume_type['qos_specs_id'])
|
||||
mock_get_context = self.mock_object(context, 'get_admin_context')
|
||||
|
||||
result = na_utils.get_backend_qos_spec_from_volume_type(volume_type)
|
||||
|
||||
self.assertEqual(None, result)
|
||||
self.assertEqual(0, mock_get_context.call_count)
|
||||
|
||||
def test_get_backend_qos_spec_from_volume_type_no_qos_spec(self):
|
||||
volume_type = fake.VOLUME_TYPE
|
||||
self.mock_object(context, 'get_admin_context')
|
||||
mock_get_specs = self.mock_object(qos_specs, 'get_qos_specs')
|
||||
mock_get_specs.return_value = None
|
||||
|
||||
result = na_utils.get_backend_qos_spec_from_volume_type(volume_type)
|
||||
|
||||
self.assertEqual(None, result)
|
||||
|
||||
def test_get_backend_qos_spec_from_volume_type_with_frontend_spec(self):
|
||||
volume_type = fake.VOLUME_TYPE
|
||||
self.mock_object(context, 'get_admin_context')
|
||||
mock_get_specs = self.mock_object(qos_specs, 'get_qos_specs')
|
||||
mock_get_specs.return_value = fake.OUTER_FRONTEND_QOS_SPEC
|
||||
|
||||
result = na_utils.get_backend_qos_spec_from_volume_type(volume_type)
|
||||
|
||||
self.assertEqual(None, result)
|
||||
|
||||
def test_get_backend_qos_spec_from_volume_type_with_backend_spec(self):
|
||||
volume_type = fake.VOLUME_TYPE
|
||||
self.mock_object(context, 'get_admin_context')
|
||||
mock_get_specs = self.mock_object(qos_specs, 'get_qos_specs')
|
||||
mock_get_specs.return_value = fake.OUTER_BACKEND_QOS_SPEC
|
||||
|
||||
result = na_utils.get_backend_qos_spec_from_volume_type(volume_type)
|
||||
|
||||
self.assertEqual(fake.QOS_SPEC, result)
|
||||
|
||||
def test_get_backend_qos_spec_from_volume_type_with_both_spec(self):
|
||||
volume_type = fake.VOLUME_TYPE
|
||||
self.mock_object(context, 'get_admin_context')
|
||||
mock_get_specs = self.mock_object(qos_specs, 'get_qos_specs')
|
||||
mock_get_specs.return_value = fake.OUTER_BOTH_QOS_SPEC
|
||||
|
||||
result = na_utils.get_backend_qos_spec_from_volume_type(volume_type)
|
||||
|
||||
self.assertEqual(fake.QOS_SPEC, result)
|
||||
|
||||
def test_check_for_invalid_qos_spec_combination(self):
|
||||
|
||||
self.assertRaises(exception.Invalid,
|
||||
na_utils.check_for_invalid_qos_spec_combination,
|
||||
fake.INVALID_QOS_POLICY_GROUP_INFO,
|
||||
fake.VOLUME_TYPE)
|
||||
|
||||
def test_get_legacy_qos_policy(self):
|
||||
extra_specs = fake.LEGACY_EXTRA_SPECS
|
||||
expected = {'policy_name': fake.QOS_POLICY_GROUP_NAME}
|
||||
|
||||
result = na_utils.get_legacy_qos_policy(extra_specs)
|
||||
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_get_legacy_qos_policy_no_policy_name(self):
|
||||
extra_specs = fake.EXTRA_SPECS
|
||||
|
||||
result = na_utils.get_legacy_qos_policy(extra_specs)
|
||||
|
||||
self.assertEqual(None, result)
|
||||
|
||||
|
||||
class OpenStackInfoTestCase(test.TestCase):
|
||||
|
||||
|
@ -351,34 +735,3 @@ class OpenStackInfoTestCase(test.TestCase):
|
|||
info._update_openstack_info()
|
||||
|
||||
self.assertTrue(mock_updt_from_dpkg.called)
|
||||
|
||||
def test_iscsi_connection_properties(self):
|
||||
|
||||
actual_properties = na_utils.get_iscsi_connection_properties(
|
||||
fake.ISCSI_FAKE_LUN_ID, fake.ISCSI_FAKE_VOLUME,
|
||||
fake.ISCSI_FAKE_IQN, fake.ISCSI_FAKE_ADDRESS,
|
||||
fake.ISCSI_FAKE_PORT)
|
||||
|
||||
actual_properties_mapped = actual_properties['data']
|
||||
|
||||
self.assertDictEqual(actual_properties_mapped,
|
||||
fake.FC_ISCSI_TARGET_INFO_DICT)
|
||||
|
||||
def test_iscsi_connection_lun_id_type_str(self):
|
||||
FAKE_LUN_ID = '1'
|
||||
|
||||
actual_properties = na_utils.get_iscsi_connection_properties(
|
||||
FAKE_LUN_ID, fake.ISCSI_FAKE_VOLUME, fake.ISCSI_FAKE_IQN,
|
||||
fake.ISCSI_FAKE_ADDRESS, fake.ISCSI_FAKE_PORT)
|
||||
|
||||
actual_properties_mapped = actual_properties['data']
|
||||
|
||||
self.assertIs(type(actual_properties_mapped['target_lun']), int)
|
||||
|
||||
def test_iscsi_connection_lun_id_type_dict(self):
|
||||
FAKE_LUN_ID = {'id': 'fake_id'}
|
||||
|
||||
self.assertRaises(TypeError, na_utils.get_iscsi_connection_properties,
|
||||
FAKE_LUN_ID, fake.ISCSI_FAKE_VOLUME,
|
||||
fake.ISCSI_FAKE_IQN, fake.ISCSI_FAKE_ADDRESS,
|
||||
fake.ISCSI_FAKE_PORT)
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
# Copyright (c) 2014 Alex Meade. All rights reserved.
|
||||
# Copyright (c) 2014 Andrew Kerr. All rights reserved.
|
||||
# Copyright (c) 2014 Jeff Applewhite. All rights reserved.
|
||||
# Copyright (c) 2015 Tom Barron. 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
|
||||
|
@ -108,11 +109,14 @@ class NetAppBlockStorage7modeLibrary(block_base.
|
|||
super(NetAppBlockStorage7modeLibrary, self).check_for_setup_error()
|
||||
|
||||
def _create_lun(self, volume_name, lun_name, size,
|
||||
metadata, qos_policy_group=None):
|
||||
metadata, qos_policy_group_name=None):
|
||||
"""Creates a LUN, handling Data ONTAP differences as needed."""
|
||||
|
||||
if qos_policy_group_name is not None:
|
||||
msg = _('Data ONTAP operating in 7-Mode does not support QoS '
|
||||
'policy groups.')
|
||||
raise exception.VolumeDriverException(msg)
|
||||
self.zapi_client.create_lun(
|
||||
volume_name, lun_name, size, metadata, qos_policy_group)
|
||||
volume_name, lun_name, size, metadata, qos_policy_group_name)
|
||||
|
||||
self.vol_refresh_voluntary = True
|
||||
|
||||
|
@ -176,8 +180,14 @@ class NetAppBlockStorage7modeLibrary(block_base.
|
|||
return False
|
||||
|
||||
def _clone_lun(self, name, new_name, space_reserved='true',
|
||||
src_block=0, dest_block=0, block_count=0):
|
||||
qos_policy_group_name=None, src_block=0, dest_block=0,
|
||||
block_count=0):
|
||||
"""Clone LUN with the given handle to the new name."""
|
||||
if qos_policy_group_name is not None:
|
||||
msg = _('Data ONTAP operating in 7-Mode does not support QoS '
|
||||
'policy groups.')
|
||||
raise exception.VolumeDriverException(msg)
|
||||
|
||||
metadata = self._get_lun_attr(name, 'metadata')
|
||||
path = metadata['Path']
|
||||
(parent, _splitter, name) = path.rpartition('/')
|
||||
|
@ -321,6 +331,7 @@ class NetAppBlockStorage7modeLibrary(block_base.
|
|||
"""Driver entry point for destroying existing volumes."""
|
||||
super(NetAppBlockStorage7modeLibrary, self).delete_volume(volume)
|
||||
self.vol_refresh_voluntary = True
|
||||
LOG.debug('Deleted LUN with name %s', volume['name'])
|
||||
|
||||
def _is_lun_valid_on_storage(self, lun):
|
||||
"""Validate LUN specific to storage system."""
|
||||
|
@ -330,19 +341,29 @@ class NetAppBlockStorage7modeLibrary(block_base.
|
|||
return False
|
||||
return True
|
||||
|
||||
def _check_volume_type_for_lun(self, volume, lun, existing_ref):
|
||||
"""Check if lun satisfies volume type."""
|
||||
extra_specs = na_utils.get_volume_extra_specs(volume)
|
||||
if extra_specs and extra_specs.pop('netapp:qos_policy_group', None):
|
||||
def _check_volume_type_for_lun(self, volume, lun, existing_ref,
|
||||
extra_specs):
|
||||
"""Check if LUN satisfies volume type."""
|
||||
if extra_specs:
|
||||
legacy_policy = extra_specs.get('netapp:qos_policy_group')
|
||||
if legacy_policy is not None:
|
||||
raise exception.ManageExistingVolumeTypeMismatch(
|
||||
reason=_("Setting LUN QoS policy group is not supported "
|
||||
"on this storage family and ONTAP version."))
|
||||
volume_type = na_utils.get_volume_type_from_volume(volume)
|
||||
if volume_type is None:
|
||||
return
|
||||
spec = na_utils.get_backend_qos_spec_from_volume_type(volume_type)
|
||||
if spec is not None:
|
||||
raise exception.ManageExistingVolumeTypeMismatch(
|
||||
reason=_("Setting LUN QoS policy group is not supported"
|
||||
" on this storage family and ONTAP version."))
|
||||
reason=_("Back-end QoS specs are not supported on this "
|
||||
"storage family and ONTAP version."))
|
||||
|
||||
def _get_preferred_target_from_list(self, target_details_list):
|
||||
def _get_preferred_target_from_list(self, target_details_list,
|
||||
filter=None):
|
||||
# 7-mode iSCSI LIFs migrate from controller to controller
|
||||
# in failover and flap operational state in transit, so
|
||||
# we don't filter these on operational state.
|
||||
|
||||
return (super(NetAppBlockStorage7modeLibrary, self)
|
||||
._get_preferred_target_from_list(target_details_list,
|
||||
filter=None))
|
||||
._get_preferred_target_from_list(target_details_list))
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
# Copyright (c) 2014 Alex Meade. All rights reserved.
|
||||
# Copyright (c) 2014 Andrew Kerr. All rights reserved.
|
||||
# Copyright (c) 2014 Jeff Applewhite. All rights reserved.
|
||||
# Copyright (c) 2015 Tom Barron. 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
|
||||
|
@ -60,8 +61,8 @@ class NetAppLun(object):
|
|||
{'prop': prop, 'name': name})
|
||||
|
||||
def __str__(self, *args, **kwargs):
|
||||
return 'NetApp Lun[handle:%s, name:%s, size:%s, metadata:%s]'\
|
||||
% (self.handle, self.name, self.size, self.metadata)
|
||||
return 'NetApp LUN [handle:%s, name:%s, size:%s, metadata:%s]' % (
|
||||
self.handle, self.name, self.size, self.metadata)
|
||||
|
||||
|
||||
class NetAppBlockStorageLibrary(object):
|
||||
|
@ -69,7 +70,6 @@ class NetAppBlockStorageLibrary(object):
|
|||
|
||||
# do not increment this as it may be used in volume type definitions
|
||||
VERSION = "1.0.0"
|
||||
IGROUP_PREFIX = 'openstack-'
|
||||
REQUIRED_FLAGS = ['netapp_login', 'netapp_password',
|
||||
'netapp_server_hostname']
|
||||
ALLOWED_LUN_OS_TYPES = ['linux', 'aix', 'hpux', 'image', 'windows',
|
||||
|
@ -94,7 +94,6 @@ class NetAppBlockStorageLibrary(object):
|
|||
self.host_type = None
|
||||
self.lookup_service = fczm_utils.create_lookup_service()
|
||||
self.app_version = kwargs.get("app_version", "unknown")
|
||||
self.db = kwargs.get('db')
|
||||
|
||||
self.configuration = kwargs['configuration']
|
||||
self.configuration.append_config_values(na_opts.netapp_connection_opts)
|
||||
|
@ -146,42 +145,54 @@ class NetAppBlockStorageLibrary(object):
|
|||
LOG.debug('create_volume on %s', volume['host'])
|
||||
|
||||
# get Data ONTAP volume name as pool name
|
||||
ontap_volume_name = volume_utils.extract_host(volume['host'],
|
||||
level='pool')
|
||||
pool_name = volume_utils.extract_host(volume['host'], level='pool')
|
||||
|
||||
if ontap_volume_name is None:
|
||||
if pool_name is None:
|
||||
msg = _("Pool is not available in the volume host field.")
|
||||
raise exception.InvalidHost(reason=msg)
|
||||
|
||||
extra_specs = na_utils.get_volume_extra_specs(volume)
|
||||
|
||||
lun_name = volume['name']
|
||||
|
||||
# start with default size, get requested size
|
||||
default_size = units.Mi * 100 # 100 MB
|
||||
size = default_size if not int(volume['size'])\
|
||||
else int(volume['size']) * units.Gi
|
||||
size = int(volume['size']) * units.Gi
|
||||
|
||||
metadata = {'OsType': self.lun_ostype,
|
||||
'SpaceReserved': 'true',
|
||||
'Path': '/vol/%s/%s' % (ontap_volume_name, lun_name)}
|
||||
'Path': '/vol/%s/%s' % (pool_name, lun_name)}
|
||||
|
||||
extra_specs = na_utils.get_volume_extra_specs(volume)
|
||||
qos_policy_group = extra_specs.pop('netapp:qos_policy_group', None) \
|
||||
if extra_specs else None
|
||||
qos_policy_group_info = self._setup_qos_for_volume(volume, extra_specs)
|
||||
qos_policy_group_name = (
|
||||
na_utils.get_qos_policy_group_name_from_info(
|
||||
qos_policy_group_info))
|
||||
|
||||
# warn on obsolete extra specs
|
||||
na_utils.log_extra_spec_warnings(extra_specs)
|
||||
try:
|
||||
self._create_lun(pool_name, lun_name, size, metadata,
|
||||
qos_policy_group_name)
|
||||
except Exception:
|
||||
LOG.exception(_LE("Exception creating LUN %(name)s in pool "
|
||||
"%(pool)s."),
|
||||
{'name': lun_name, 'pool': pool_name})
|
||||
self._mark_qos_policy_group_for_deletion(qos_policy_group_info)
|
||||
msg = _("Volume %s could not be created.")
|
||||
raise exception.VolumeBackendAPIException(data=msg % (
|
||||
volume['name']))
|
||||
LOG.debug('Created LUN with name %(name)s and QoS info %(qos)s',
|
||||
{'name': lun_name, 'qos': qos_policy_group_info})
|
||||
|
||||
self._create_lun(ontap_volume_name, lun_name, size,
|
||||
metadata, qos_policy_group)
|
||||
LOG.debug('Created LUN with name %s', lun_name)
|
||||
|
||||
metadata['Path'] = '/vol/%s/%s' % (ontap_volume_name, lun_name)
|
||||
metadata['Volume'] = ontap_volume_name
|
||||
metadata['Path'] = '/vol/%s/%s' % (pool_name, lun_name)
|
||||
metadata['Volume'] = pool_name
|
||||
metadata['Qtree'] = None
|
||||
|
||||
handle = self._create_lun_handle(metadata)
|
||||
self._add_lun_to_table(NetAppLun(handle, lun_name, size, metadata))
|
||||
|
||||
def _setup_qos_for_volume(self, volume, extra_specs):
|
||||
return None
|
||||
|
||||
def _mark_qos_policy_group_for_deletion(self, qos_policy_group_info):
|
||||
return
|
||||
|
||||
def delete_volume(self, volume):
|
||||
"""Driver entry point for destroying existing volumes."""
|
||||
name = volume['name']
|
||||
|
@ -222,7 +233,7 @@ class NetAppBlockStorageLibrary(object):
|
|||
vol_name = snapshot['volume_name']
|
||||
snapshot_name = snapshot['name']
|
||||
lun = self._get_lun_from_table(vol_name)
|
||||
self._clone_lun(lun.name, snapshot_name, 'false')
|
||||
self._clone_lun(lun.name, snapshot_name, space_reserved='false')
|
||||
|
||||
def delete_snapshot(self, snapshot):
|
||||
"""Driver entry point for deleting a snapshot."""
|
||||
|
@ -230,28 +241,60 @@ class NetAppBlockStorageLibrary(object):
|
|||
LOG.debug("Snapshot %s deletion successful", snapshot['name'])
|
||||
|
||||
def create_volume_from_snapshot(self, volume, snapshot):
|
||||
"""Driver entry point for creating a new volume from a snapshot.
|
||||
source = {'name': snapshot['name'], 'size': snapshot['volume_size']}
|
||||
return self._clone_source_to_destination(source, volume)
|
||||
|
||||
Many would call this "cloning" and in fact we use cloning to implement
|
||||
this feature.
|
||||
"""
|
||||
def create_cloned_volume(self, volume, src_vref):
|
||||
src_lun = self._get_lun_from_table(src_vref['name'])
|
||||
source = {'name': src_lun.name, 'size': src_vref['size']}
|
||||
return self._clone_source_to_destination(source, volume)
|
||||
|
||||
vol_size = volume['size']
|
||||
snap_size = snapshot['volume_size']
|
||||
snapshot_name = snapshot['name']
|
||||
new_name = volume['name']
|
||||
self._clone_lun(snapshot_name, new_name, 'true')
|
||||
if vol_size != snap_size:
|
||||
try:
|
||||
self.extend_volume(volume, volume['size'])
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
LOG.error(
|
||||
_LE("Resizing %s failed. Cleaning volume."), new_name)
|
||||
self.delete_volume(volume)
|
||||
def _clone_source_to_destination(self, source, destination_volume):
|
||||
source_size = source['size']
|
||||
destination_size = destination_volume['size']
|
||||
|
||||
source_name = source['name']
|
||||
destination_name = destination_volume['name']
|
||||
|
||||
extra_specs = na_utils.get_volume_extra_specs(destination_volume)
|
||||
|
||||
qos_policy_group_info = self._setup_qos_for_volume(
|
||||
destination_volume, extra_specs)
|
||||
qos_policy_group_name = (
|
||||
na_utils.get_qos_policy_group_name_from_info(
|
||||
qos_policy_group_info))
|
||||
|
||||
try:
|
||||
self._clone_lun(source_name, destination_name,
|
||||
space_reserved='true',
|
||||
qos_policy_group_name=qos_policy_group_name)
|
||||
|
||||
if destination_size != source_size:
|
||||
|
||||
try:
|
||||
self.extend_volume(
|
||||
destination_volume, destination_size,
|
||||
qos_policy_group_name=qos_policy_group_name)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
LOG.error(
|
||||
_LE("Resizing %s failed. Cleaning volume."),
|
||||
destination_volume['id'])
|
||||
self.delete_volume(destination_volume)
|
||||
|
||||
except Exception:
|
||||
LOG.exception(_LE("Exception cloning volume %(name)s from source "
|
||||
"volume %(source)s."),
|
||||
{'name': destination_name, 'source': source_name})
|
||||
|
||||
self._mark_qos_policy_group_for_deletion(qos_policy_group_info)
|
||||
|
||||
msg = _("Volume %s could not be created from source volume.")
|
||||
raise exception.VolumeBackendAPIException(
|
||||
data=msg % destination_name)
|
||||
|
||||
def _create_lun(self, volume_name, lun_name, size,
|
||||
metadata, qos_policy_group=None):
|
||||
metadata, qos_policy_group_name=None):
|
||||
"""Creates a LUN, handling Data ONTAP differences as needed."""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
@ -338,7 +381,7 @@ class NetAppBlockStorageLibrary(object):
|
|||
def _create_igroup_add_initiators(self, initiator_group_type,
|
||||
host_os_type, initiator_list):
|
||||
"""Creates igroup and adds initiators."""
|
||||
igroup_name = self.IGROUP_PREFIX + six.text_type(uuid.uuid4())
|
||||
igroup_name = na_utils.OPENSTACK_PREFIX + six.text_type(uuid.uuid4())
|
||||
self.zapi_client.create_igroup(igroup_name, initiator_group_type,
|
||||
host_os_type)
|
||||
for initiator in initiator_list:
|
||||
|
@ -367,7 +410,8 @@ class NetAppBlockStorageLibrary(object):
|
|||
return lun
|
||||
|
||||
def _clone_lun(self, name, new_name, space_reserved='true',
|
||||
src_block=0, dest_block=0, block_count=0):
|
||||
qos_policy_group_name=None, src_block=0, dest_block=0,
|
||||
block_count=0):
|
||||
"""Clone LUN with the given name to the new name."""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
@ -388,22 +432,6 @@ class NetAppBlockStorageLibrary(object):
|
|||
def _get_fc_target_wwpns(self, include_partner=True):
|
||||
raise NotImplementedError()
|
||||
|
||||
def create_cloned_volume(self, volume, src_vref):
|
||||
"""Creates a clone of the specified volume."""
|
||||
vol_size = volume['size']
|
||||
src_vol = self._get_lun_from_table(src_vref['name'])
|
||||
src_vol_size = src_vref['size']
|
||||
new_name = volume['name']
|
||||
self._clone_lun(src_vol.name, new_name, 'true')
|
||||
if vol_size != src_vol_size:
|
||||
try:
|
||||
self.extend_volume(volume, volume['size'])
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
LOG.error(
|
||||
_LE("Resizing %s failed. Cleaning volume."), new_name)
|
||||
self.delete_volume(volume)
|
||||
|
||||
def get_volume_stats(self, refresh=False):
|
||||
"""Get volume stats.
|
||||
|
||||
|
@ -418,7 +446,7 @@ class NetAppBlockStorageLibrary(object):
|
|||
def _update_volume_stats(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
def extend_volume(self, volume, new_size):
|
||||
def extend_volume(self, volume, new_size, qos_policy_group_name=None):
|
||||
"""Extend an existing volume to the new size."""
|
||||
name = volume['name']
|
||||
lun = self._get_lun_from_table(name)
|
||||
|
@ -434,7 +462,9 @@ class NetAppBlockStorageLibrary(object):
|
|||
int(new_size_bytes)):
|
||||
self.zapi_client.do_direct_resize(path, new_size_bytes)
|
||||
else:
|
||||
self._do_sub_clone_resize(path, new_size_bytes)
|
||||
self._do_sub_clone_resize(
|
||||
path, new_size_bytes,
|
||||
qos_policy_group_name=qos_policy_group_name)
|
||||
self.lun_table[name].size = new_size_bytes
|
||||
else:
|
||||
LOG.info(_LI("No need to extend volume %s"
|
||||
|
@ -450,7 +480,8 @@ class NetAppBlockStorageLibrary(object):
|
|||
break
|
||||
return value
|
||||
|
||||
def _do_sub_clone_resize(self, path, new_size_bytes):
|
||||
def _do_sub_clone_resize(self, path, new_size_bytes,
|
||||
qos_policy_group_name=None):
|
||||
"""Does sub LUN clone after verification.
|
||||
|
||||
Clones the block ranges and swaps
|
||||
|
@ -476,10 +507,13 @@ class NetAppBlockStorageLibrary(object):
|
|||
' as it contains no blocks.')
|
||||
raise exception.VolumeBackendAPIException(data=msg % name)
|
||||
new_lun = 'new-%s' % name
|
||||
self.zapi_client.create_lun(vol_name, new_lun, new_size_bytes,
|
||||
metadata)
|
||||
self.zapi_client.create_lun(
|
||||
vol_name, new_lun, new_size_bytes, metadata,
|
||||
qos_policy_group_name=qos_policy_group_name)
|
||||
try:
|
||||
self._clone_lun(name, new_lun, block_count=block_count)
|
||||
self._clone_lun(name, new_lun, block_count=block_count,
|
||||
qos_policy_group_name=qos_policy_group_name)
|
||||
|
||||
self._post_sub_clone_resize(path)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
|
@ -531,7 +565,8 @@ class NetAppBlockStorageLibrary(object):
|
|||
block_count = ls / bs
|
||||
return block_count
|
||||
|
||||
def _check_volume_type_for_lun(self, volume, lun, existing_ref):
|
||||
def _check_volume_type_for_lun(self, volume, lun, existing_ref,
|
||||
extra_specs):
|
||||
"""Checks if lun satifies the volume type."""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
@ -543,9 +578,19 @@ class NetAppBlockStorageLibrary(object):
|
|||
source-name: complete lun path eg. /vol/vol0/lun.
|
||||
"""
|
||||
lun = self._get_existing_vol_with_manage_ref(existing_ref)
|
||||
self._check_volume_type_for_lun(volume, lun, existing_ref)
|
||||
|
||||
extra_specs = na_utils.get_volume_extra_specs(volume)
|
||||
|
||||
self._check_volume_type_for_lun(volume, lun, existing_ref, extra_specs)
|
||||
|
||||
qos_policy_group_info = self._setup_qos_for_volume(volume, extra_specs)
|
||||
qos_policy_group_name = (
|
||||
na_utils.get_qos_policy_group_name_from_info(
|
||||
qos_policy_group_info))
|
||||
|
||||
path = lun.get_metadata_property('Path')
|
||||
if lun.name == volume['name']:
|
||||
new_path = path
|
||||
LOG.info(_LI("LUN with given ref %s need not be renamed "
|
||||
"during manage operation."), existing_ref)
|
||||
else:
|
||||
|
@ -554,6 +599,9 @@ class NetAppBlockStorageLibrary(object):
|
|||
self.zapi_client.move_lun(path, new_path)
|
||||
lun = self._get_existing_vol_with_manage_ref(
|
||||
{'source-name': new_path})
|
||||
if qos_policy_group_name is not None:
|
||||
self.zapi_client.set_lun_qos_policy_group(new_path,
|
||||
qos_policy_group_name)
|
||||
self._add_lun_to_table(lun)
|
||||
LOG.info(_LI("Manage operation completed for LUN with new path"
|
||||
" %(path)s and uuid %(uuid)s."),
|
||||
|
@ -742,8 +790,8 @@ class NetAppBlockStorageLibrary(object):
|
|||
LOG.debug("Mapped LUN %(name)s to the initiator(s) %(initiators)s",
|
||||
{'name': volume_name, 'initiators': initiators})
|
||||
|
||||
target_wwpns, initiator_target_map, num_paths = \
|
||||
self._build_initiator_target_map(connector)
|
||||
target_wwpns, initiator_target_map, num_paths = (
|
||||
self._build_initiator_target_map(connector))
|
||||
|
||||
if target_wwpns:
|
||||
LOG.debug("Successfully fetched target details for LUN %(name)s "
|
||||
|
@ -795,8 +843,8 @@ class NetAppBlockStorageLibrary(object):
|
|||
LOG.info(_LI("Need to remove FC Zone, building initiator "
|
||||
"target map"))
|
||||
|
||||
target_wwpns, initiator_target_map, num_paths = \
|
||||
self._build_initiator_target_map(connector)
|
||||
target_wwpns, initiator_target_map, num_paths = (
|
||||
self._build_initiator_target_map(connector))
|
||||
|
||||
info['data'] = {'target_wwn': target_wwpns,
|
||||
'initiator_target_map': initiator_target_map}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
# Copyright (c) 2014 Alex Meade. All rights reserved.
|
||||
# Copyright (c) 2014 Andrew Kerr. All rights reserved.
|
||||
# Copyright (c) 2014 Jeff Applewhite. All rights reserved.
|
||||
# Copyright (c) 2015 Tom Barron. 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
|
||||
|
@ -28,10 +29,10 @@ from oslo_utils import units
|
|||
import six
|
||||
|
||||
from cinder import exception
|
||||
from cinder.i18n import _, _LE
|
||||
from cinder.i18n import _
|
||||
from cinder.openstack.common import loopingcall
|
||||
from cinder import utils
|
||||
from cinder.volume.drivers.netapp.dataontap import block_base
|
||||
from cinder.volume.drivers.netapp.dataontap.client import api as netapp_api
|
||||
from cinder.volume.drivers.netapp.dataontap.client import client_cmode
|
||||
from cinder.volume.drivers.netapp.dataontap import ssc_cmode
|
||||
from cinder.volume.drivers.netapp import options as na_opts
|
||||
|
@ -39,6 +40,7 @@ from cinder.volume.drivers.netapp import utils as na_utils
|
|||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
QOS_CLEANUP_INTERVAL_SECONDS = 60
|
||||
|
||||
|
||||
class NetAppBlockStorageCmodeLibrary(block_base.
|
||||
|
@ -75,13 +77,22 @@ class NetAppBlockStorageCmodeLibrary(block_base.
|
|||
"""Check that the driver is working and can communicate."""
|
||||
ssc_cmode.check_ssc_api_permissions(self.zapi_client)
|
||||
super(NetAppBlockStorageCmodeLibrary, self).check_for_setup_error()
|
||||
self._start_periodic_tasks()
|
||||
|
||||
def _start_periodic_tasks(self):
|
||||
# Start the task that harvests soft-deleted QoS policy groups.
|
||||
harvest_qos_periodic_task = loopingcall.FixedIntervalLoopingCall(
|
||||
self.zapi_client.remove_unused_qos_policy_groups)
|
||||
harvest_qos_periodic_task.start(
|
||||
interval=QOS_CLEANUP_INTERVAL_SECONDS,
|
||||
initial_delay=QOS_CLEANUP_INTERVAL_SECONDS)
|
||||
|
||||
def _create_lun(self, volume_name, lun_name, size,
|
||||
metadata, qos_policy_group=None):
|
||||
metadata, qos_policy_group_name=None):
|
||||
"""Creates a LUN, handling Data ONTAP differences as needed."""
|
||||
|
||||
self.zapi_client.create_lun(
|
||||
volume_name, lun_name, size, metadata, qos_policy_group)
|
||||
volume_name, lun_name, size, metadata, qos_policy_group_name)
|
||||
|
||||
self._update_stale_vols(
|
||||
volume=ssc_cmode.NetAppVolume(volume_name, self.vserver))
|
||||
|
@ -98,19 +109,22 @@ class NetAppBlockStorageCmodeLibrary(block_base.
|
|||
if initiator_igroups and lun_maps:
|
||||
for igroup in initiator_igroups:
|
||||
igroup_name = igroup['initiator-group-name']
|
||||
if igroup_name.startswith(self.IGROUP_PREFIX):
|
||||
if igroup_name.startswith(na_utils.OPENSTACK_PREFIX):
|
||||
for lun_map in lun_maps:
|
||||
if lun_map['initiator-group'] == igroup_name:
|
||||
return igroup_name, lun_map['lun-id']
|
||||
return None, None
|
||||
|
||||
def _clone_lun(self, name, new_name, space_reserved='true',
|
||||
src_block=0, dest_block=0, block_count=0):
|
||||
qos_policy_group_name=None, src_block=0, dest_block=0,
|
||||
block_count=0):
|
||||
"""Clone LUN with the given handle to the new name."""
|
||||
metadata = self._get_lun_attr(name, 'metadata')
|
||||
volume = metadata['Volume']
|
||||
self.zapi_client.clone_lun(volume, name, new_name, space_reserved,
|
||||
src_block=0, dest_block=0, block_count=0)
|
||||
qos_policy_group_name=qos_policy_group_name,
|
||||
src_block=0, dest_block=0,
|
||||
block_count=0)
|
||||
LOG.debug("Cloned LUN with new name %s", new_name)
|
||||
lun = self.zapi_client.get_lun_by_args(vserver=self.vserver,
|
||||
path='/vol/%s/%s'
|
||||
|
@ -181,7 +195,7 @@ class NetAppBlockStorageCmodeLibrary(block_base.
|
|||
for vol in self.ssc_vols['all']:
|
||||
pool = dict()
|
||||
pool['pool_name'] = vol.id['name']
|
||||
pool['QoS_support'] = False
|
||||
pool['QoS_support'] = True
|
||||
pool['reserved_percentage'] = 0
|
||||
|
||||
# convert sizes to GB and de-rate by NetApp multiplier
|
||||
|
@ -241,15 +255,23 @@ class NetAppBlockStorageCmodeLibrary(block_base.
|
|||
if lun:
|
||||
netapp_vol = lun.get_metadata_property('Volume')
|
||||
super(NetAppBlockStorageCmodeLibrary, self).delete_volume(volume)
|
||||
try:
|
||||
qos_policy_group_info = na_utils.get_valid_qos_policy_group_info(
|
||||
volume)
|
||||
except exception.Invalid:
|
||||
# Delete even if there was invalid qos policy specified for the
|
||||
# volume.
|
||||
qos_policy_group_info = None
|
||||
self._mark_qos_policy_group_for_deletion(qos_policy_group_info)
|
||||
if netapp_vol:
|
||||
self._update_stale_vols(
|
||||
volume=ssc_cmode.NetAppVolume(netapp_vol, self.vserver))
|
||||
msg = 'Deleted LUN with name %(name)s and QoS info %(qos)s'
|
||||
LOG.debug(msg, {'name': volume['name'], 'qos': qos_policy_group_info})
|
||||
|
||||
def _check_volume_type_for_lun(self, volume, lun, existing_ref):
|
||||
def _check_volume_type_for_lun(self, volume, lun, existing_ref,
|
||||
extra_specs):
|
||||
"""Check if LUN satisfies volume type."""
|
||||
extra_specs = na_utils.get_volume_extra_specs(volume)
|
||||
match_write = False
|
||||
|
||||
def scan_ssc_data():
|
||||
volumes = ssc_cmode.get_volumes_for_specs(self.ssc_vols,
|
||||
extra_specs)
|
||||
|
@ -264,27 +286,15 @@ class NetAppBlockStorageCmodeLibrary(block_base.
|
|||
self, self.zapi_client.get_connection(), self.vserver)
|
||||
match_read = scan_ssc_data()
|
||||
|
||||
qos_policy_group = extra_specs.pop('netapp:qos_policy_group', None) \
|
||||
if extra_specs else None
|
||||
if qos_policy_group:
|
||||
if match_read:
|
||||
try:
|
||||
path = lun.get_metadata_property('Path')
|
||||
self.zapi_client.set_lun_qos_policy_group(path,
|
||||
qos_policy_group)
|
||||
match_write = True
|
||||
except netapp_api.NaApiError as nae:
|
||||
LOG.error(_LE("Failure setting QoS policy group. %s"), nae)
|
||||
else:
|
||||
match_write = True
|
||||
if not (match_read and match_write):
|
||||
if not match_read:
|
||||
raise exception.ManageExistingVolumeTypeMismatch(
|
||||
reason=(_("LUN with given ref %(ref)s does not satisfy volume"
|
||||
" type. Ensure LUN volume with ssc features is"
|
||||
" present on vserver %(vs)s.")
|
||||
% {'ref': existing_ref, 'vs': self.vserver}))
|
||||
|
||||
def _get_preferred_target_from_list(self, target_details_list):
|
||||
def _get_preferred_target_from_list(self, target_details_list,
|
||||
filter=None):
|
||||
# cDOT iSCSI LIFs do not migrate from controller to controller
|
||||
# in failover. Rather, an iSCSI LIF must be configured on each
|
||||
# controller and the initiator has to take responsibility for
|
||||
|
@ -305,3 +315,33 @@ class NetAppBlockStorageCmodeLibrary(block_base.
|
|||
return (super(NetAppBlockStorageCmodeLibrary, self)
|
||||
._get_preferred_target_from_list(target_details_list,
|
||||
filter=operational_addresses))
|
||||
|
||||
def _setup_qos_for_volume(self, volume, extra_specs):
|
||||
try:
|
||||
qos_policy_group_info = na_utils.get_valid_qos_policy_group_info(
|
||||
volume, extra_specs)
|
||||
except exception.Invalid:
|
||||
msg = _('Invalid QoS specification detected while getting QoS '
|
||||
'policy for volume %s') % volume['id']
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
self.zapi_client.provision_qos_policy_group(qos_policy_group_info)
|
||||
return qos_policy_group_info
|
||||
|
||||
def _mark_qos_policy_group_for_deletion(self, qos_policy_group_info):
|
||||
self.zapi_client.mark_qos_policy_group_for_deletion(
|
||||
qos_policy_group_info)
|
||||
|
||||
def unmanage(self, volume):
|
||||
"""Removes the specified volume from Cinder management.
|
||||
|
||||
Does not delete the underlying backend storage object.
|
||||
"""
|
||||
try:
|
||||
qos_policy_group_info = na_utils.get_valid_qos_policy_group_info(
|
||||
volume)
|
||||
except exception.Invalid:
|
||||
# Unmanage even if there was invalid qos policy specified for the
|
||||
# volume.
|
||||
qos_policy_group_info = None
|
||||
self._mark_qos_policy_group_for_deletion(qos_policy_group_info)
|
||||
super(NetAppBlockStorageCmodeLibrary, self).unmanage(volume)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
# Copyright (c) 2014 Alex Meade. All rights reserved.
|
||||
# Copyright (c) 2014 Clinton Knight. All rights reserved.
|
||||
# Copyright (c) 2015 Tom Barron. 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
|
||||
|
@ -67,7 +68,7 @@ class Client(object):
|
|||
return self.connection.invoke_successfully(request, enable_tunneling)
|
||||
|
||||
def create_lun(self, volume_name, lun_name, size, metadata,
|
||||
qos_policy_group=None):
|
||||
qos_policy_group_name=None):
|
||||
"""Issues API request for creating LUN on volume."""
|
||||
|
||||
path = '/vol/%s/%s' % (volume_name, lun_name)
|
||||
|
@ -76,8 +77,8 @@ class Client(object):
|
|||
**{'path': path, 'size': six.text_type(size),
|
||||
'ostype': metadata['OsType'],
|
||||
'space-reservation-enabled': metadata['SpaceReserved']})
|
||||
if qos_policy_group:
|
||||
lun_create.add_new_child('qos-policy-group', qos_policy_group)
|
||||
if qos_policy_group_name:
|
||||
lun_create.add_new_child('qos-policy-group', qos_policy_group_name)
|
||||
|
||||
try:
|
||||
self.connection.invoke_successfully(lun_create, True)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
# Copyright (c) 2014 Alex Meade. All rights reserved.
|
||||
# Copyright (c) 2014 Clinton Knight. All rights reserved.
|
||||
# Copyright (c) 2015 Tom Barron. 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
|
||||
|
@ -21,13 +22,14 @@ from oslo_log import log as logging
|
|||
import six
|
||||
|
||||
from cinder import exception
|
||||
from cinder.i18n import _
|
||||
from cinder.i18n import _, _LW
|
||||
from cinder.volume.drivers.netapp.dataontap.client import api as netapp_api
|
||||
from cinder.volume.drivers.netapp.dataontap.client import client_base
|
||||
from cinder.volume.drivers.netapp import utils as na_utils
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
DELETED_PREFIX = 'deleted_cinder_'
|
||||
|
||||
|
||||
class Client(client_base.Client):
|
||||
|
@ -236,7 +238,8 @@ class Client(client_base.Client):
|
|||
return igroup_list
|
||||
|
||||
def clone_lun(self, volume, name, new_name, space_reserved='true',
|
||||
src_block=0, dest_block=0, block_count=0):
|
||||
qos_policy_group_name=None, src_block=0, dest_block=0,
|
||||
block_count=0):
|
||||
# zAPI can only handle 2^24 blocks per range
|
||||
bc_limit = 2 ** 24 # 8GB
|
||||
# zAPI can only handle 32 block ranges per call
|
||||
|
@ -257,6 +260,9 @@ class Client(client_base.Client):
|
|||
**{'volume': volume, 'source-path': name,
|
||||
'destination-path': new_name,
|
||||
'space-reserve': space_reserved})
|
||||
if qos_policy_group_name is not None:
|
||||
clone_create.add_new_child('qos-policy-group-name',
|
||||
qos_policy_group_name)
|
||||
if block_count > 0:
|
||||
block_ranges = netapp_api.NaElement("block-ranges")
|
||||
segments = int(math.ceil(block_count / float(bc_limit)))
|
||||
|
@ -295,22 +301,111 @@ class Client(client_base.Client):
|
|||
return []
|
||||
return attr_list.get_children()
|
||||
|
||||
def file_assign_qos(self, flex_vol, qos_policy_group, file_path):
|
||||
"""Retrieves LUN with specified args."""
|
||||
file_assign_qos = netapp_api.NaElement.create_node_with_children(
|
||||
'file-assign-qos',
|
||||
**{'volume': flex_vol,
|
||||
'qos-policy-group-name': qos_policy_group,
|
||||
'file': file_path,
|
||||
'vserver': self.vserver})
|
||||
self.connection.invoke_successfully(file_assign_qos, True)
|
||||
def file_assign_qos(self, flex_vol, qos_policy_group_name, file_path):
|
||||
"""Assigns the named QoS policy-group to a file."""
|
||||
api_args = {
|
||||
'volume': flex_vol,
|
||||
'qos-policy-group-name': qos_policy_group_name,
|
||||
'file': file_path,
|
||||
'vserver': self.vserver,
|
||||
}
|
||||
return self.send_request('file-assign-qos', api_args, False)
|
||||
|
||||
def provision_qos_policy_group(self, qos_policy_group_info):
|
||||
"""Create QOS policy group on the backend if appropriate."""
|
||||
if qos_policy_group_info is None:
|
||||
return
|
||||
|
||||
# Legacy QOS uses externally provisioned QOS policy group,
|
||||
# so we don't need to create one on the backend.
|
||||
legacy = qos_policy_group_info.get('legacy')
|
||||
if legacy is not None:
|
||||
return
|
||||
|
||||
spec = qos_policy_group_info.get('spec')
|
||||
if spec is not None:
|
||||
self.qos_policy_group_create(spec['policy_name'],
|
||||
spec['max_throughput'])
|
||||
|
||||
def qos_policy_group_create(self, qos_policy_group_name, max_throughput):
|
||||
"""Creates a QOS policy group."""
|
||||
api_args = {
|
||||
'policy-group': qos_policy_group_name,
|
||||
'max-throughput': max_throughput,
|
||||
'vserver': self.vserver,
|
||||
}
|
||||
return self.send_request('qos-policy-group-create', api_args, False)
|
||||
|
||||
def qos_policy_group_delete(self, qos_policy_group_name):
|
||||
"""Attempts to delete a QOS policy group."""
|
||||
api_args = {
|
||||
'policy-group': qos_policy_group_name,
|
||||
}
|
||||
return self.send_request('qos-policy-group-delete', api_args, False)
|
||||
|
||||
def qos_policy_group_rename(self, qos_policy_group_name, new_name):
|
||||
"""Renames a QOS policy group."""
|
||||
api_args = {
|
||||
'policy-group-name': qos_policy_group_name,
|
||||
'new-name': new_name,
|
||||
}
|
||||
return self.send_request('qos-policy-group-rename', api_args, False)
|
||||
|
||||
def mark_qos_policy_group_for_deletion(self, qos_policy_group_info):
|
||||
"""Do (soft) delete of backing QOS policy group for a cinder volume."""
|
||||
if qos_policy_group_info is None:
|
||||
return
|
||||
|
||||
spec = qos_policy_group_info.get('spec')
|
||||
|
||||
# For cDOT we want to delete the QoS policy group that we created for
|
||||
# this cinder volume. Because the QoS policy may still be "in use"
|
||||
# after the zapi call to delete the volume itself returns successfully,
|
||||
# we instead rename the QoS policy group using a specific pattern and
|
||||
# later attempt on a best effort basis to delete any QoS policy groups
|
||||
# matching that pattern.
|
||||
if spec is not None:
|
||||
current_name = spec['policy_name']
|
||||
new_name = DELETED_PREFIX + current_name
|
||||
try:
|
||||
self.qos_policy_group_rename(current_name, new_name)
|
||||
except netapp_api.NaApiError as ex:
|
||||
msg = _LW('Rename failure in cleanup of cDOT QOS policy group '
|
||||
'%(name)s: %(ex)s')
|
||||
LOG.warning(msg, {'name': current_name, 'ex': ex})
|
||||
|
||||
# Attempt to delete any QoS policies named "delete-openstack-*".
|
||||
self.remove_unused_qos_policy_groups()
|
||||
|
||||
def remove_unused_qos_policy_groups(self):
|
||||
"""Deletes all QOS policy groups that are marked for deletion."""
|
||||
api_args = {
|
||||
'query': {
|
||||
'qos-policy-group-info': {
|
||||
'policy-group': '%s*' % DELETED_PREFIX,
|
||||
'vserver': self.vserver,
|
||||
}
|
||||
},
|
||||
'max-records': 3500,
|
||||
'continue-on-failure': 'true',
|
||||
'return-success-list': 'false',
|
||||
'return-failure-list': 'false',
|
||||
}
|
||||
|
||||
try:
|
||||
self.send_request('qos-policy-group-delete-iter', api_args, False)
|
||||
except netapp_api.NaApiError as ex:
|
||||
msg = 'Could not delete QOS policy groups. Details: %(ex)s'
|
||||
msg_args = {'ex': ex}
|
||||
LOG.debug(msg % msg_args)
|
||||
|
||||
def set_lun_qos_policy_group(self, path, qos_policy_group):
|
||||
"""Sets qos_policy_group on a LUN."""
|
||||
set_qos_group = netapp_api.NaElement.create_node_with_children(
|
||||
'lun-set-qos-policy-group',
|
||||
**{'path': path, 'qos-policy-group': qos_policy_group})
|
||||
self.connection.invoke_successfully(set_qos_group, True)
|
||||
api_args = {
|
||||
'path': path,
|
||||
'qos-policy-group': qos_policy_group,
|
||||
}
|
||||
return self.send_request('lun-set-qos-policy-group', api_args)
|
||||
|
||||
def get_if_info_by_ip(self, ip):
|
||||
"""Gets the network interface info by ip."""
|
||||
|
@ -327,7 +422,7 @@ class Client(client_base.Client):
|
|||
attr_list = result.get_child_by_name('attributes-list')
|
||||
return attr_list.get_children()
|
||||
raise exception.NotFound(
|
||||
_('No interface found on cluster for ip %s') % (ip))
|
||||
_('No interface found on cluster for ip %s') % ip)
|
||||
|
||||
def get_vol_by_junc_vserver(self, vserver, junction):
|
||||
"""Gets the volume by junction path and vserver."""
|
||||
|
|
|
@ -24,12 +24,11 @@ Volume driver for NetApp NFS storage.
|
|||
from oslo_log import log as logging
|
||||
|
||||
from cinder import exception
|
||||
from cinder.i18n import _, _LE, _LI
|
||||
from cinder.i18n import _
|
||||
from cinder.volume.drivers.netapp.dataontap.client import client_7mode
|
||||
from cinder.volume.drivers.netapp.dataontap import nfs_base
|
||||
from cinder.volume.drivers.netapp import options as na_opts
|
||||
from cinder.volume.drivers.netapp import utils as na_utils
|
||||
from cinder.volume import utils as volume_utils
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
@ -54,6 +53,8 @@ class NetApp7modeNfsDriver(nfs_base.NetAppNfsDriver):
|
|||
port=self.configuration.netapp_server_port,
|
||||
vfiler=self.configuration.netapp_vfiler)
|
||||
|
||||
self.ssc_enabled = False
|
||||
|
||||
def check_for_setup_error(self):
|
||||
"""Checks if setup occurred properly."""
|
||||
api_version = self.zapi_client.get_ontapi_version()
|
||||
|
@ -68,42 +69,10 @@ class NetApp7modeNfsDriver(nfs_base.NetAppNfsDriver):
|
|||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
super(NetApp7modeNfsDriver, self).check_for_setup_error()
|
||||
|
||||
def create_volume(self, volume):
|
||||
"""Creates a volume.
|
||||
def _clone_backing_file_for_volume(self, volume_name, clone_name,
|
||||
volume_id, share=None):
|
||||
"""Clone backing file for Cinder volume."""
|
||||
|
||||
:param volume: volume reference
|
||||
"""
|
||||
LOG.debug('create_volume on %s', volume['host'])
|
||||
self._ensure_shares_mounted()
|
||||
|
||||
# get share as pool name
|
||||
share = volume_utils.extract_host(volume['host'], level='pool')
|
||||
|
||||
if share is None:
|
||||
msg = _("Pool is not available in the volume host field.")
|
||||
raise exception.InvalidHost(reason=msg)
|
||||
|
||||
volume['provider_location'] = share
|
||||
LOG.info(_LI('Creating volume at location %s'),
|
||||
volume['provider_location'])
|
||||
|
||||
try:
|
||||
self._do_create_volume(volume)
|
||||
except Exception as ex:
|
||||
LOG.error(_LE("Exception creating vol %(name)s on "
|
||||
"share %(share)s. Details: %(ex)s"),
|
||||
{'name': volume['name'],
|
||||
'share': volume['provider_location'],
|
||||
'ex': ex})
|
||||
msg = _("Volume %s could not be created on shares.")
|
||||
raise exception.VolumeBackendAPIException(
|
||||
data=msg % (volume['name']))
|
||||
|
||||
return {'provider_location': volume['provider_location']}
|
||||
|
||||
def _clone_volume(self, volume_name, clone_name,
|
||||
volume_id, share=None):
|
||||
"""Clones mounted volume with NetApp filer."""
|
||||
(_host_ip, export_path) = self._get_export_ip_path(volume_id, share)
|
||||
storage_path = self.zapi_client.get_actual_path_for_export(export_path)
|
||||
target_path = '%s/%s' % (storage_path, clone_name)
|
||||
|
@ -200,12 +169,21 @@ class NetApp7modeNfsDriver(nfs_base.NetAppNfsDriver):
|
|||
"""Checks if share is compatible with volume to host it."""
|
||||
return self._is_share_eligible(share, volume['size'])
|
||||
|
||||
def _check_volume_type(self, volume, share, file_name):
|
||||
def _check_volume_type(self, volume, share, file_name, extra_specs):
|
||||
"""Matches a volume type for share file."""
|
||||
extra_specs = na_utils.get_volume_extra_specs(volume)
|
||||
qos_policy_group = extra_specs.pop('netapp:qos_policy_group', None) \
|
||||
if extra_specs else None
|
||||
if qos_policy_group:
|
||||
raise exception.ManageExistingVolumeTypeMismatch(
|
||||
reason=(_("Setting file qos policy group is not supported"
|
||||
" on this storage family and ontap version.")))
|
||||
volume_type = na_utils.get_volume_type_from_volume(volume)
|
||||
if volume_type and 'qos_spec_id' in volume_type:
|
||||
raise exception.ManageExistingVolumeTypeMismatch(
|
||||
reason=_("QoS specs are not supported"
|
||||
" on this storage family and ONTAP version."))
|
||||
|
||||
def _do_qos_for_volume(self, volume, extra_specs, cleanup=False):
|
||||
"""Set QoS policy on backend from volume type information."""
|
||||
# 7-mode DOT does not support QoS.
|
||||
return
|
||||
|
|
|
@ -31,8 +31,8 @@ import time
|
|||
from oslo_concurrency import processutils
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import excutils
|
||||
from oslo_utils import units
|
||||
import six
|
||||
import six.moves.urllib.parse as urlparse
|
||||
|
||||
from cinder import exception
|
||||
|
@ -42,9 +42,11 @@ from cinder import utils
|
|||
from cinder.volume.drivers.netapp import options as na_opts
|
||||
from cinder.volume.drivers.netapp import utils as na_utils
|
||||
from cinder.volume.drivers import nfs
|
||||
from cinder.volume import utils as volume_utils
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class NetAppNfsDriver(nfs.NfsDriver):
|
||||
|
@ -75,6 +77,7 @@ class NetAppNfsDriver(nfs.NfsDriver):
|
|||
self._context = context
|
||||
na_utils.check_flags(self.REQUIRED_FLAGS, self.configuration)
|
||||
self.zapi_client = None
|
||||
self.ssc_enabled = False
|
||||
|
||||
def check_for_setup_error(self):
|
||||
"""Returns an error if prerequisites aren't met."""
|
||||
|
@ -88,39 +91,128 @@ class NetAppNfsDriver(nfs.NfsDriver):
|
|||
"""
|
||||
return volume['provider_location']
|
||||
|
||||
def create_volume(self, volume):
|
||||
"""Creates a volume.
|
||||
|
||||
:param volume: volume reference
|
||||
"""
|
||||
LOG.debug('create_volume on %s', volume['host'])
|
||||
self._ensure_shares_mounted()
|
||||
|
||||
# get share as pool name
|
||||
pool_name = volume_utils.extract_host(volume['host'], level='pool')
|
||||
|
||||
if pool_name is None:
|
||||
msg = _("Pool is not available in the volume host field.")
|
||||
raise exception.InvalidHost(reason=msg)
|
||||
|
||||
extra_specs = na_utils.get_volume_extra_specs(volume)
|
||||
|
||||
try:
|
||||
volume['provider_location'] = pool_name
|
||||
LOG.debug('Using pool %s.', pool_name)
|
||||
self._do_create_volume(volume)
|
||||
self._do_qos_for_volume(volume, extra_specs)
|
||||
return {'provider_location': volume['provider_location']}
|
||||
except Exception:
|
||||
LOG.exception(_LE("Exception creating vol %(name)s on "
|
||||
"pool %(pool)s."),
|
||||
{'name': volume['name'],
|
||||
'pool': volume['provider_location']})
|
||||
# We need to set this for the model update in order for the
|
||||
# manager to behave correctly.
|
||||
volume['provider_location'] = None
|
||||
finally:
|
||||
if self.ssc_enabled:
|
||||
self._update_stale_vols(self._get_vol_for_share(pool_name))
|
||||
|
||||
msg = _("Volume %(vol)s could not be created in pool %(pool)s.")
|
||||
raise exception.VolumeBackendAPIException(data=msg % {
|
||||
'vol': volume['name'], 'pool': pool_name})
|
||||
|
||||
def create_volume_from_snapshot(self, volume, snapshot):
|
||||
"""Creates a volume from a snapshot."""
|
||||
vol_size = volume.size
|
||||
snap_size = snapshot.volume_size
|
||||
source = {
|
||||
'name': snapshot['name'],
|
||||
'size': snapshot['volume_size'],
|
||||
'id': snapshot['volume_id'],
|
||||
}
|
||||
return self._clone_source_to_destination_volume(source, volume)
|
||||
|
||||
self._clone_volume(snapshot.name, volume.name, snapshot.volume_id)
|
||||
share = self._get_volume_location(snapshot.volume_id)
|
||||
volume['provider_location'] = share
|
||||
path = self.local_path(volume)
|
||||
run_as_root = self._execute_as_root
|
||||
def create_cloned_volume(self, volume, src_vref):
|
||||
"""Creates a clone of the specified volume."""
|
||||
source = {'name': src_vref['name'],
|
||||
'size': src_vref['size'],
|
||||
'id': src_vref['id']}
|
||||
|
||||
return self._clone_source_to_destination_volume(source, volume)
|
||||
|
||||
def _clone_source_to_destination_volume(self, source, destination_volume):
|
||||
share = self._get_volume_location(source['id'])
|
||||
|
||||
extra_specs = na_utils.get_volume_extra_specs(destination_volume)
|
||||
|
||||
try:
|
||||
destination_volume['provider_location'] = share
|
||||
self._clone_with_extension_check(
|
||||
source, destination_volume)
|
||||
self._do_qos_for_volume(destination_volume, extra_specs)
|
||||
return {'provider_location': destination_volume[
|
||||
'provider_location']}
|
||||
except Exception:
|
||||
LOG.exception(_LE("Exception creating volume %(name)s from source "
|
||||
"%(source)s on share %(share)s."),
|
||||
{'name': destination_volume['id'],
|
||||
'source': source['name'],
|
||||
'share': destination_volume['provider_location']})
|
||||
msg = _("Volume %s could not be created on shares.")
|
||||
raise exception.VolumeBackendAPIException(data=msg % (
|
||||
destination_volume['id']))
|
||||
|
||||
def _clone_with_extension_check(self, source, destination_volume):
|
||||
source_size = source['size']
|
||||
source_id = source['id']
|
||||
source_name = source['name']
|
||||
destination_volume_size = destination_volume['size']
|
||||
self._clone_backing_file_for_volume(source_name,
|
||||
destination_volume['name'],
|
||||
source_id)
|
||||
path = self.local_path(destination_volume)
|
||||
if self._discover_file_till_timeout(path):
|
||||
self._set_rw_permissions(path)
|
||||
if vol_size != snap_size:
|
||||
if destination_volume_size != source_size:
|
||||
try:
|
||||
self.extend_volume(volume, vol_size)
|
||||
self.extend_volume(destination_volume,
|
||||
destination_volume_size)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
LOG.error(
|
||||
_LE("Resizing %s failed. Cleaning volume."),
|
||||
volume.name)
|
||||
self._execute('rm', path, run_as_root=run_as_root)
|
||||
LOG.error(_LE("Resizing %s failed. Cleaning "
|
||||
"volume."), destination_volume['name'])
|
||||
self._cleanup_volume_on_failure(destination_volume)
|
||||
raise exception.CinderException(
|
||||
_("Resizing clone %s failed.")
|
||||
% destination_volume['name'])
|
||||
else:
|
||||
raise exception.CinderException(
|
||||
_("NFS file %s not discovered.") % volume['name'])
|
||||
raise exception.CinderException(_("NFS file %s not discovered.")
|
||||
% destination_volume['name'])
|
||||
|
||||
return {'provider_location': volume['provider_location']}
|
||||
def _cleanup_volume_on_failure(self, volume):
|
||||
LOG.debug('Cleaning up, failed operation on %s', volume['name'])
|
||||
vol_path = self.local_path(volume)
|
||||
if os.path.exists(vol_path):
|
||||
LOG.debug('Found %s, deleting ...', vol_path)
|
||||
self._delete_file_at_path(vol_path)
|
||||
else:
|
||||
LOG.debug('Could not find %s, continuing ...', vol_path)
|
||||
|
||||
def _do_qos_for_volume(self, volume, extra_specs, cleanup=False):
|
||||
"""Set QoS policy on backend from volume type information."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def create_snapshot(self, snapshot):
|
||||
"""Creates a snapshot."""
|
||||
self._clone_volume(snapshot['volume_name'],
|
||||
snapshot['name'],
|
||||
snapshot['volume_id'])
|
||||
self._clone_backing_file_for_volume(snapshot['volume_name'],
|
||||
snapshot['name'],
|
||||
snapshot['volume_id'])
|
||||
|
||||
def delete_snapshot(self, snapshot):
|
||||
"""Deletes a snapshot."""
|
||||
|
@ -138,8 +230,9 @@ class NetAppNfsDriver(nfs.NfsDriver):
|
|||
export_path = self._get_export_path(volume_id)
|
||||
return nfs_server_ip + ':' + export_path
|
||||
|
||||
def _clone_volume(self, volume_name, clone_name, volume_id, share=None):
|
||||
"""Clones mounted volume using NetApp API."""
|
||||
def _clone_backing_file_for_volume(self, volume_name, clone_name,
|
||||
volume_id, share=None):
|
||||
"""Clone backing file for Cinder volume."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def _get_provider_location(self, volume_id):
|
||||
|
@ -194,33 +287,6 @@ class NetAppNfsDriver(nfs.NfsDriver):
|
|||
return os.path.join(self._get_mount_point_for_share(nfs_share),
|
||||
volume_name)
|
||||
|
||||
def create_cloned_volume(self, volume, src_vref):
|
||||
"""Creates a clone of the specified volume."""
|
||||
vol_size = volume.size
|
||||
src_vol_size = src_vref.size
|
||||
self._clone_volume(src_vref.name, volume.name, src_vref.id)
|
||||
share = self._get_volume_location(src_vref.id)
|
||||
volume['provider_location'] = share
|
||||
path = self.local_path(volume)
|
||||
|
||||
if self._discover_file_till_timeout(path):
|
||||
self._set_rw_permissions(path)
|
||||
if vol_size != src_vol_size:
|
||||
try:
|
||||
self.extend_volume(volume, vol_size)
|
||||
except Exception:
|
||||
LOG.error(
|
||||
_LE("Resizing %s failed. Cleaning volume."),
|
||||
volume.name)
|
||||
self._execute('rm', path,
|
||||
run_as_root=self._execute_as_root)
|
||||
raise
|
||||
else:
|
||||
raise exception.CinderException(
|
||||
_("NFS file %s not discovered.") % volume['name'])
|
||||
|
||||
return {'provider_location': volume['provider_location']}
|
||||
|
||||
def _update_volume_stats(self):
|
||||
"""Retrieve stats info from volume group."""
|
||||
raise NotImplementedError()
|
||||
|
@ -230,7 +296,7 @@ class NetAppNfsDriver(nfs.NfsDriver):
|
|||
super(NetAppNfsDriver, self).copy_image_to_volume(
|
||||
context, volume, image_service, image_id)
|
||||
LOG.info(_LI('Copied image to volume %s using regular download.'),
|
||||
volume['name'])
|
||||
volume['id'])
|
||||
self._register_image_in_cache(volume, image_id)
|
||||
|
||||
def _register_image_in_cache(self, volume, image_id):
|
||||
|
@ -269,7 +335,8 @@ class NetAppNfsDriver(nfs.NfsDriver):
|
|||
file_path = '%s/%s' % (dir, dst)
|
||||
if not os.path.exists(file_path):
|
||||
LOG.info(_LI('Cloning from cache to destination %s'), dst)
|
||||
self._clone_volume(src, dst, volume_id=None, share=share)
|
||||
self._clone_backing_file_for_volume(src, dst, volume_id=None,
|
||||
share=share)
|
||||
_do_clone()
|
||||
|
||||
@utils.synchronized('clean_cache')
|
||||
|
@ -351,7 +418,7 @@ class NetAppNfsDriver(nfs.NfsDriver):
|
|||
|
||||
@utils.synchronized(f[0], external=True)
|
||||
def _do_delete():
|
||||
if self._delete_file(file_path):
|
||||
if self._delete_file_at_path(file_path):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
@ -360,7 +427,7 @@ class NetAppNfsDriver(nfs.NfsDriver):
|
|||
if bytes_to_free <= 0:
|
||||
return
|
||||
|
||||
def _delete_file(self, path):
|
||||
def _delete_file_at_path(self, path):
|
||||
"""Delete file from disk and return result as boolean."""
|
||||
try:
|
||||
LOG.debug('Deleting file at path %s', path)
|
||||
|
@ -386,6 +453,9 @@ class NetAppNfsDriver(nfs.NfsDriver):
|
|||
image_id = image_meta['id']
|
||||
cloned = False
|
||||
post_clone = False
|
||||
|
||||
extra_specs = na_utils.get_volume_extra_specs(volume)
|
||||
|
||||
try:
|
||||
cache_result = self._find_image_in_cache(image_id)
|
||||
if cache_result:
|
||||
|
@ -394,16 +464,13 @@ class NetAppNfsDriver(nfs.NfsDriver):
|
|||
cloned = self._direct_nfs_clone(volume, image_location,
|
||||
image_id)
|
||||
if cloned:
|
||||
self._do_qos_for_volume(volume, extra_specs)
|
||||
post_clone = self._post_clone_image(volume)
|
||||
except Exception as e:
|
||||
msg = e.msg if getattr(e, 'msg', None) else e
|
||||
LOG.info(_LI('Image cloning unsuccessful for image'
|
||||
' %(image_id)s. Message: %(msg)s'),
|
||||
{'image_id': image_id, 'msg': msg})
|
||||
vol_path = self.local_path(volume)
|
||||
volume['provider_location'] = None
|
||||
if os.path.exists(vol_path):
|
||||
self._delete_file(vol_path)
|
||||
finally:
|
||||
cloned = cloned and post_clone
|
||||
share = volume['provider_location'] if cloned else None
|
||||
|
@ -438,7 +505,6 @@ class NetAppNfsDriver(nfs.NfsDriver):
|
|||
image_location = self._construct_image_nfs_url(image_location)
|
||||
share = self._is_cloneable_share(image_location)
|
||||
run_as_root = self._execute_as_root
|
||||
|
||||
if share and self._is_share_vol_compatible(volume, share):
|
||||
LOG.debug('Share is cloneable %s', share)
|
||||
volume['provider_location'] = share
|
||||
|
@ -449,7 +515,7 @@ class NetAppNfsDriver(nfs.NfsDriver):
|
|||
run_as_root=run_as_root)
|
||||
if img_info.file_format == 'raw':
|
||||
LOG.debug('Image is raw %s', image_id)
|
||||
self._clone_volume(
|
||||
self._clone_backing_file_for_volume(
|
||||
img_file, volume['name'],
|
||||
volume_id=None, share=share)
|
||||
cloned = True
|
||||
|
@ -701,7 +767,7 @@ class NetAppNfsDriver(nfs.NfsDriver):
|
|||
export_path = nfs_share.rsplit(':', 1)[1]
|
||||
return self.zapi_client.get_flexvol_capacity(export_path)
|
||||
|
||||
def _check_volume_type(self, volume, share, file_name):
|
||||
def _check_volume_type(self, volume, share, file_name, extra_specs):
|
||||
"""Match volume type for share file."""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
@ -796,7 +862,11 @@ class NetAppNfsDriver(nfs.NfsDriver):
|
|||
LOG.debug("Asked to manage NFS volume %(vol)s, with vol ref %(ref)s",
|
||||
{'vol': volume['id'],
|
||||
'ref': existing_vol_ref['source-name']})
|
||||
self._check_volume_type(volume, nfs_share, vol_path)
|
||||
|
||||
extra_specs = na_utils.get_volume_extra_specs(volume)
|
||||
|
||||
self._check_volume_type(volume, nfs_share, vol_path, extra_specs)
|
||||
|
||||
if vol_path == volume['name']:
|
||||
LOG.debug("New Cinder volume %s name matches reference name: "
|
||||
"no need to rename.", volume['name'])
|
||||
|
@ -815,6 +885,14 @@ class NetAppNfsDriver(nfs.NfsDriver):
|
|||
{'name': existing_vol_ref['source-name'],
|
||||
'msg': err})
|
||||
raise exception.VolumeBackendAPIException(data=exception_msg)
|
||||
try:
|
||||
self._do_qos_for_volume(volume, extra_specs, cleanup=False)
|
||||
except Exception as err:
|
||||
exception_msg = (_("Failed to set QoS for existing volume "
|
||||
"%(name)s, Error msg: %(msg)s.") %
|
||||
{'name': existing_vol_ref['source-name'],
|
||||
'msg': six.text_type(err)})
|
||||
raise exception.VolumeBackendAPIException(data=exception_msg)
|
||||
return {'provider_location': nfs_share}
|
||||
|
||||
def manage_existing_get_size(self, volume, existing_vol_ref):
|
||||
|
@ -857,8 +935,16 @@ class NetAppNfsDriver(nfs.NfsDriver):
|
|||
|
||||
:param volume: Cinder volume to unmanage
|
||||
"""
|
||||
CONF = cfg.CONF
|
||||
vol_str = CONF.volume_name_template % volume['id']
|
||||
vol_path = os.path.join(volume['provider_location'], vol_str)
|
||||
LOG.info(_LI("Cinder NFS volume with current path \"%(cr)s\" is "
|
||||
"no longer being managed."), {'cr': vol_path})
|
||||
|
||||
@utils.synchronized('update_stale')
|
||||
def _update_stale_vols(self, volume=None, reset=False):
|
||||
"""Populates stale vols with vol and returns set copy."""
|
||||
raise NotImplementedError
|
||||
|
||||
def _get_vol_for_share(self, nfs_share):
|
||||
"""Gets the ssc vol with given share."""
|
||||
raise NotImplementedError
|
||||
|
|
|
@ -25,13 +25,14 @@ import os
|
|||
import uuid
|
||||
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import excutils
|
||||
import six
|
||||
|
||||
from cinder import exception
|
||||
from cinder.i18n import _, _LE, _LI, _LW
|
||||
from cinder.image import image_utils
|
||||
from cinder.openstack.common import loopingcall
|
||||
from cinder import utils
|
||||
from cinder.volume.drivers.netapp.dataontap.client import api as na_api
|
||||
from cinder.volume.drivers.netapp.dataontap.client import client_cmode
|
||||
from cinder.volume.drivers.netapp.dataontap import nfs_base
|
||||
from cinder.volume.drivers.netapp.dataontap import ssc_cmode
|
||||
|
@ -41,6 +42,7 @@ from cinder.volume import utils as volume_utils
|
|||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
QOS_CLEANUP_INTERVAL_SECONDS = 60
|
||||
|
||||
|
||||
class NetAppCmodeNfsDriver(nfs_base.NetAppNfsDriver):
|
||||
|
@ -75,85 +77,55 @@ class NetAppCmodeNfsDriver(nfs_base.NetAppNfsDriver):
|
|||
"""Check that the driver is working and can communicate."""
|
||||
super(NetAppCmodeNfsDriver, self).check_for_setup_error()
|
||||
ssc_cmode.check_ssc_api_permissions(self.zapi_client)
|
||||
self._start_periodic_tasks()
|
||||
|
||||
def create_volume(self, volume):
|
||||
"""Creates a volume.
|
||||
|
||||
:param volume: volume reference
|
||||
"""
|
||||
LOG.debug('create_volume on %s', volume['host'])
|
||||
self._ensure_shares_mounted()
|
||||
|
||||
# get share as pool name
|
||||
share = volume_utils.extract_host(volume['host'], level='pool')
|
||||
|
||||
if share is None:
|
||||
msg = _("Pool is not available in the volume host field.")
|
||||
raise exception.InvalidHost(reason=msg)
|
||||
|
||||
extra_specs = na_utils.get_volume_extra_specs(volume)
|
||||
qos_policy_group = extra_specs.pop('netapp:qos_policy_group', None) \
|
||||
if extra_specs else None
|
||||
|
||||
# warn on obsolete extra specs
|
||||
na_utils.log_extra_spec_warnings(extra_specs)
|
||||
|
||||
def _do_qos_for_volume(self, volume, extra_specs, cleanup=True):
|
||||
try:
|
||||
volume['provider_location'] = share
|
||||
LOG.info(_LI('casted to %s'), volume['provider_location'])
|
||||
self._do_create_volume(volume)
|
||||
if qos_policy_group:
|
||||
self._set_qos_policy_group_on_volume(volume, share,
|
||||
qos_policy_group)
|
||||
return {'provider_location': volume['provider_location']}
|
||||
except Exception as ex:
|
||||
LOG.error(_LE("Exception creating vol %(name)s on "
|
||||
"share %(share)s. Details: %(ex)s"),
|
||||
{'name': volume['name'],
|
||||
'share': volume['provider_location'],
|
||||
'ex': ex})
|
||||
volume['provider_location'] = None
|
||||
finally:
|
||||
if self.ssc_enabled:
|
||||
self._update_stale_vols(self._get_vol_for_share(share))
|
||||
qos_policy_group_info = na_utils.get_valid_qos_policy_group_info(
|
||||
volume, extra_specs)
|
||||
self.zapi_client.provision_qos_policy_group(qos_policy_group_info)
|
||||
self._set_qos_policy_group_on_volume(volume, qos_policy_group_info)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
LOG.error(_LE("Setting QoS for %s failed"), volume['id'])
|
||||
if cleanup:
|
||||
LOG.debug("Cleaning volume %s", volume['id'])
|
||||
self._cleanup_volume_on_failure(volume)
|
||||
|
||||
msg = _("Volume %s could not be created on shares.")
|
||||
raise exception.VolumeBackendAPIException(data=msg % (volume['name']))
|
||||
def _start_periodic_tasks(self):
|
||||
# Start the task that harvests soft-deleted QoS policy groups.
|
||||
harvest_qos_periodic_task = loopingcall.FixedIntervalLoopingCall(
|
||||
self.zapi_client.remove_unused_qos_policy_groups)
|
||||
harvest_qos_periodic_task.start(
|
||||
interval=QOS_CLEANUP_INTERVAL_SECONDS,
|
||||
initial_delay=QOS_CLEANUP_INTERVAL_SECONDS)
|
||||
|
||||
def _set_qos_policy_group_on_volume(self, volume, share, qos_policy_group):
|
||||
def _set_qos_policy_group_on_volume(self, volume, qos_policy_group_info):
|
||||
if qos_policy_group_info is None:
|
||||
return
|
||||
qos_policy_group_name = na_utils.get_qos_policy_group_name_from_info(
|
||||
qos_policy_group_info)
|
||||
if qos_policy_group_name is None:
|
||||
return
|
||||
target_path = '%s' % (volume['name'])
|
||||
share = volume_utils.extract_host(volume['host'], level='pool')
|
||||
export_path = share.split(':')[1]
|
||||
flex_vol_name = self.zapi_client.get_vol_by_junc_vserver(self.vserver,
|
||||
export_path)
|
||||
self.zapi_client.file_assign_qos(flex_vol_name,
|
||||
qos_policy_group,
|
||||
qos_policy_group_name,
|
||||
target_path)
|
||||
|
||||
def _check_volume_type(self, volume, share, file_name):
|
||||
def _check_volume_type(self, volume, share, file_name, extra_specs):
|
||||
"""Match volume type for share file."""
|
||||
extra_specs = na_utils.get_volume_extra_specs(volume)
|
||||
qos_policy_group = extra_specs.pop('netapp:qos_policy_group', None) \
|
||||
if extra_specs else None
|
||||
if not self._is_share_vol_type_match(volume, share):
|
||||
raise exception.ManageExistingVolumeTypeMismatch(
|
||||
reason=(_("Volume type does not match for share %s."),
|
||||
share))
|
||||
if qos_policy_group:
|
||||
try:
|
||||
vserver, flex_vol_name = self._get_vserver_and_exp_vol(
|
||||
share=share)
|
||||
self.zapi_client.file_assign_qos(flex_vol_name,
|
||||
qos_policy_group,
|
||||
file_name)
|
||||
except na_api.NaApiError as ex:
|
||||
LOG.exception(_LE('Setting file QoS policy group failed. %s'),
|
||||
ex)
|
||||
raise exception.NetAppDriverException(
|
||||
reason=(_('Setting file QoS policy group failed. %s'), ex))
|
||||
|
||||
def _clone_volume(self, volume_name, clone_name,
|
||||
volume_id, share=None):
|
||||
"""Clones mounted volume on NetApp Cluster."""
|
||||
def _clone_backing_file_for_volume(self, volume_name, clone_name,
|
||||
volume_id, share=None):
|
||||
"""Clone backing file for Cinder volume."""
|
||||
(vserver, exp_volume) = self._get_vserver_and_exp_vol(volume_id, share)
|
||||
self.zapi_client.clone_file(exp_volume, volume_name, clone_name,
|
||||
vserver)
|
||||
|
@ -202,7 +174,7 @@ class NetAppCmodeNfsDriver(nfs_base.NetAppNfsDriver):
|
|||
|
||||
pool = dict()
|
||||
pool['pool_name'] = nfs_share
|
||||
pool['QoS_support'] = False
|
||||
pool['QoS_support'] = True
|
||||
pool.update(capacity)
|
||||
|
||||
# add SSC content if available
|
||||
|
@ -280,10 +252,10 @@ class NetAppCmodeNfsDriver(nfs_base.NetAppNfsDriver):
|
|||
file_list = []
|
||||
(vserver, exp_volume) = self._get_vserver_and_exp_vol(
|
||||
volume_id=None, share=share)
|
||||
for file in old_files:
|
||||
path = '/vol/%s/%s' % (exp_volume, file)
|
||||
for old_file in old_files:
|
||||
path = '/vol/%s/%s' % (exp_volume, old_file)
|
||||
u_bytes = self.zapi_client.get_file_usage(path, vserver)
|
||||
file_list.append((file, u_bytes))
|
||||
file_list.append((old_file, u_bytes))
|
||||
LOG.debug('Shortlisted files eligible for deletion: %s', file_list)
|
||||
return file_list
|
||||
|
||||
|
@ -343,6 +315,15 @@ class NetAppCmodeNfsDriver(nfs_base.NetAppNfsDriver):
|
|||
"""Deletes a logical volume."""
|
||||
share = volume['provider_location']
|
||||
super(NetAppCmodeNfsDriver, self).delete_volume(volume)
|
||||
try:
|
||||
qos_policy_group_info = na_utils.get_valid_qos_policy_group_info(
|
||||
volume)
|
||||
self.zapi_client.mark_qos_policy_group_for_deletion(
|
||||
qos_policy_group_info)
|
||||
except Exception:
|
||||
# Don't blow up here if something went wrong de-provisioning the
|
||||
# QoS policy for the volume.
|
||||
pass
|
||||
self._post_prov_deprov_in_ssc(share)
|
||||
|
||||
def delete_snapshot(self, snapshot):
|
||||
|
@ -521,8 +502,29 @@ class NetAppCmodeNfsDriver(nfs_base.NetAppNfsDriver):
|
|||
{'img': image_id, 'vol': volume['id']})
|
||||
finally:
|
||||
if os.path.exists(dst_img_conv_local):
|
||||
self._delete_file(dst_img_conv_local)
|
||||
self._delete_file_at_path(dst_img_conv_local)
|
||||
self._post_clone_image(volume)
|
||||
finally:
|
||||
if os.path.exists(dst_img_local):
|
||||
self._delete_file(dst_img_local)
|
||||
self._delete_file_at_path(dst_img_local)
|
||||
|
||||
def unmanage(self, volume):
|
||||
"""Removes the specified volume from Cinder management.
|
||||
|
||||
Does not delete the underlying backend storage object. A log entry
|
||||
will be made to notify the Admin that the volume is no longer being
|
||||
managed.
|
||||
|
||||
:param volume: Cinder volume to unmanage
|
||||
"""
|
||||
try:
|
||||
qos_policy_group_info = na_utils.get_valid_qos_policy_group_info(
|
||||
volume)
|
||||
self.zapi_client.mark_qos_policy_group_for_deletion(
|
||||
qos_policy_group_info)
|
||||
except Exception:
|
||||
# Unmanage even if there was a problem deprovisioning the
|
||||
# associated qos policy group.
|
||||
pass
|
||||
|
||||
super(NetAppCmodeNfsDriver, self).unmanage(volume)
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
# Copyright (c) 2014 Ben Swartzlander. All rights reserved.
|
||||
# Copyright (c) 2014 Navneet Singh. All rights reserved.
|
||||
# Copyright (c) 2014 Clinton Knight. All rights reserved.
|
||||
# Copyright (c) 2015 Tom Barron. 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
|
||||
|
@ -529,7 +530,7 @@ def refresh_cluster_ssc(backend, na_server, vserver, synchronous=False):
|
|||
|
||||
def get_volumes_for_specs(ssc_vols, specs):
|
||||
"""Shortlists volumes for extra specs provided."""
|
||||
if specs is None or not isinstance(specs, dict):
|
||||
if specs is None or specs == {} or not isinstance(specs, dict):
|
||||
return ssc_vols['all']
|
||||
result = copy.deepcopy(ssc_vols['all'])
|
||||
raid_type = specs.get('netapp:raid_type')
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# Copyright (c) 2012 NetApp, Inc. All rights reserved.
|
||||
# Copyright (c) 2014 Navneet Singh. All rights reserved.
|
||||
# Copyright (c) 2014 Clinton Knight. All rights reserved.
|
||||
# Copyright (c) 2015 Tom Barron. 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
|
||||
|
@ -31,21 +32,26 @@ import six
|
|||
|
||||
from cinder import context
|
||||
from cinder import exception
|
||||
from cinder.i18n import _, _LW, _LI
|
||||
from cinder.i18n import _, _LE, _LW, _LI
|
||||
from cinder import utils
|
||||
from cinder import version
|
||||
from cinder.volume import qos_specs
|
||||
from cinder.volume import volume_types
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
OPENSTACK_PREFIX = 'openstack-'
|
||||
OBSOLETE_SSC_SPECS = {'netapp:raid_type': 'netapp_raid_type',
|
||||
'netapp:disk_type': 'netapp_disk_type'}
|
||||
DEPRECATED_SSC_SPECS = {'netapp_unmirrored': 'netapp_mirrored',
|
||||
'netapp_nodedup': 'netapp_dedup',
|
||||
'netapp_nocompression': 'netapp_compression',
|
||||
'netapp_thick_provisioned': 'netapp_thin_provisioned'}
|
||||
QOS_KEYS = frozenset(
|
||||
['maxIOPS', 'total_iops_sec', 'maxBPS', 'total_bytes_sec'])
|
||||
BACKEND_QOS_CONSUMERS = frozenset(['back-end', 'both'])
|
||||
|
||||
|
||||
def validate_instantiation(**kwargs):
|
||||
|
@ -106,11 +112,14 @@ def get_volume_extra_specs(volume):
|
|||
"""Provides extra specs associated with volume."""
|
||||
ctxt = context.get_admin_context()
|
||||
type_id = volume.get('volume_type_id')
|
||||
specs = None
|
||||
if type_id is not None:
|
||||
volume_type = volume_types.get_volume_type(ctxt, type_id)
|
||||
specs = volume_type.get('extra_specs')
|
||||
return specs
|
||||
if type_id is None:
|
||||
return {}
|
||||
volume_type = volume_types.get_volume_type(ctxt, type_id)
|
||||
if volume_type is None:
|
||||
return {}
|
||||
extra_specs = volume_type.get('extra_specs', {})
|
||||
log_extra_spec_warnings(extra_specs)
|
||||
return extra_specs
|
||||
|
||||
|
||||
def resolve_hostname(hostname):
|
||||
|
@ -159,6 +168,140 @@ def get_iscsi_connection_properties(lun_id, volume, iqn,
|
|||
}
|
||||
|
||||
|
||||
def validate_qos_spec(qos_spec):
|
||||
"""Check validity of Cinder qos spec for our backend."""
|
||||
if qos_spec is None:
|
||||
return
|
||||
normalized_qos_keys = [key.lower() for key in QOS_KEYS]
|
||||
keylist = []
|
||||
for key, value in six.iteritems(qos_spec):
|
||||
lower_case_key = key.lower()
|
||||
if lower_case_key not in normalized_qos_keys:
|
||||
msg = _('Unrecognized QOS keyword: "%s"') % key
|
||||
raise exception.Invalid(msg)
|
||||
keylist.append(lower_case_key)
|
||||
# Modify the following check when we allow multiple settings in one spec.
|
||||
if len(keylist) > 1:
|
||||
msg = _('Only one limit can be set in a QoS spec.')
|
||||
raise exception.Invalid(msg)
|
||||
|
||||
|
||||
def get_volume_type_from_volume(volume):
|
||||
"""Provides volume type associated with volume."""
|
||||
type_id = volume.get('volume_type_id')
|
||||
if type_id is None:
|
||||
return {}
|
||||
ctxt = context.get_admin_context()
|
||||
return volume_types.get_volume_type(ctxt, type_id)
|
||||
|
||||
|
||||
def map_qos_spec(qos_spec, volume):
|
||||
"""Map Cinder QOS spec to limit/throughput-value as used in client API."""
|
||||
if qos_spec is None:
|
||||
return None
|
||||
qos_spec = map_dict_to_lower(qos_spec)
|
||||
spec = dict(policy_name=get_qos_policy_group_name(volume),
|
||||
max_throughput=None)
|
||||
# IOPS and BPS specifications are exclusive of one another.
|
||||
if 'maxiops' in qos_spec or 'total_iops_sec' in qos_spec:
|
||||
spec['max_throughput'] = '%siops' % qos_spec['maxiops']
|
||||
elif 'maxbps' in qos_spec or 'total_bytes_sec' in qos_spec:
|
||||
spec['max_throughput'] = '%sB/s' % qos_spec['maxbps']
|
||||
return spec
|
||||
|
||||
|
||||
def map_dict_to_lower(input_dict):
|
||||
"""Return an equivalent to the input dictionary with lower-case keys."""
|
||||
lower_case_dict = {}
|
||||
for key in input_dict:
|
||||
lower_case_dict[key.lower()] = input_dict[key]
|
||||
return lower_case_dict
|
||||
|
||||
|
||||
def get_qos_policy_group_name(volume):
|
||||
"""Return the name of backend QOS policy group based on its volume id."""
|
||||
if 'id' in volume:
|
||||
return OPENSTACK_PREFIX + volume['id']
|
||||
return None
|
||||
|
||||
|
||||
def get_qos_policy_group_name_from_info(qos_policy_group_info):
|
||||
"""Return the name of a QOS policy group given qos policy group info."""
|
||||
if qos_policy_group_info is None:
|
||||
return None
|
||||
legacy = qos_policy_group_info.get('legacy')
|
||||
if legacy is not None:
|
||||
return legacy['policy_name']
|
||||
spec = qos_policy_group_info.get('spec')
|
||||
if spec is not None:
|
||||
return spec['policy_name']
|
||||
return None
|
||||
|
||||
|
||||
def get_valid_qos_policy_group_info(volume, extra_specs=None):
|
||||
"""Given a volume, return information for QOS provisioning."""
|
||||
info = dict(legacy=None, spec=None)
|
||||
try:
|
||||
volume_type = get_volume_type_from_volume(volume)
|
||||
except KeyError:
|
||||
LOG.exception(_LE('Cannot get QoS spec for volume %s.'), volume['id'])
|
||||
return info
|
||||
if volume_type is None:
|
||||
return info
|
||||
if extra_specs is None:
|
||||
extra_specs = volume_type.get('extra_specs', {})
|
||||
info['legacy'] = get_legacy_qos_policy(extra_specs)
|
||||
info['spec'] = get_valid_backend_qos_spec_from_volume_type(volume,
|
||||
volume_type)
|
||||
msg = 'QoS policy group info for volume %(vol)s: %(info)s'
|
||||
LOG.debug(msg, {'vol': volume['name'], 'info': info})
|
||||
check_for_invalid_qos_spec_combination(info, volume_type)
|
||||
return info
|
||||
|
||||
|
||||
def get_valid_backend_qos_spec_from_volume_type(volume, volume_type):
|
||||
"""Given a volume type, return the associated Cinder QoS spec."""
|
||||
spec_key_values = get_backend_qos_spec_from_volume_type(volume_type)
|
||||
if spec_key_values is None:
|
||||
return None
|
||||
validate_qos_spec(spec_key_values)
|
||||
return map_qos_spec(spec_key_values, volume)
|
||||
|
||||
|
||||
def get_backend_qos_spec_from_volume_type(volume_type):
|
||||
qos_specs_id = volume_type.get('qos_specs_id')
|
||||
if qos_specs_id is None:
|
||||
return None
|
||||
ctxt = context.get_admin_context()
|
||||
qos_spec = qos_specs.get_qos_specs(ctxt, qos_specs_id)
|
||||
if qos_spec is None:
|
||||
return None
|
||||
consumer = qos_spec['consumer']
|
||||
# Front end QoS specs are handled by libvirt and we ignore them here.
|
||||
if consumer not in BACKEND_QOS_CONSUMERS:
|
||||
return None
|
||||
spec_key_values = qos_spec['specs']
|
||||
return spec_key_values
|
||||
|
||||
|
||||
def check_for_invalid_qos_spec_combination(info, volume_type):
|
||||
"""Invalidate QOS spec if both legacy and non-legacy info is present."""
|
||||
if info['legacy'] and info['spec']:
|
||||
msg = _('Conflicting QoS specifications in volume type '
|
||||
'%s: when QoS spec is associated to volume '
|
||||
'type, legacy "netapp:qos_policy_group" is not allowed in '
|
||||
'the volume type extra specs.') % volume_type['id']
|
||||
raise exception.Invalid(msg)
|
||||
|
||||
|
||||
def get_legacy_qos_policy(extra_specs):
|
||||
"""Return legacy qos policy information if present in extra specs."""
|
||||
external_policy_name = extra_specs.get('netapp:qos_policy_group')
|
||||
if external_policy_name is None:
|
||||
return None
|
||||
return dict(policy_name=external_policy_name)
|
||||
|
||||
|
||||
class hashabledict(dict):
|
||||
"""A hashable dictionary that is comparable (i.e. in unit tests, etc.)"""
|
||||
def __hash__(self):
|
||||
|
|
Loading…
Reference in New Issue