Merge "Dell SC: Add support for ManageableVD"
This commit is contained in:
commit
c84976fd3d
@ -1445,3 +1445,119 @@ class DellSCSanISCSIDriverTestCase(test.TestCase):
|
||||
cgsnap['consistencygroup_id'])
|
||||
mock_delete_cg_replay.assert_called_once_with(self.SCRPLAYPROFILE,
|
||||
cgsnap['id'])
|
||||
|
||||
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||
'manage_existing')
|
||||
def test_manage_existing(self,
|
||||
mock_manage_existing,
|
||||
mock_close_connection,
|
||||
mock_open_connection,
|
||||
mock_init):
|
||||
# Very little to do in this one. The call is sent
|
||||
# straight down.
|
||||
volume = {'id': 'guid'}
|
||||
existing_ref = {'source-name': 'imavolumename'}
|
||||
self.driver.manage_existing(volume, existing_ref)
|
||||
mock_manage_existing.assert_called_once_with(volume['id'],
|
||||
existing_ref)
|
||||
|
||||
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||
'manage_existing')
|
||||
def test_manage_existing_id(self,
|
||||
mock_manage_existing,
|
||||
mock_close_connection,
|
||||
mock_open_connection,
|
||||
mock_init):
|
||||
# Very little to do in this one. The call is sent
|
||||
# straight down.
|
||||
volume = {'id': 'guid'}
|
||||
existing_ref = {'source-id': 'imadeviceid'}
|
||||
self.driver.manage_existing(volume, existing_ref)
|
||||
mock_manage_existing.assert_called_once_with(volume['id'],
|
||||
existing_ref)
|
||||
|
||||
def test_manage_existing_bad_ref(self,
|
||||
mock_close_connection,
|
||||
mock_open_connection,
|
||||
mock_init):
|
||||
volume = {'id': 'guid'}
|
||||
existing_ref = {'banana-name': 'imavolumename'}
|
||||
self.assertRaises(exception.ManageExistingInvalidReference,
|
||||
self.driver.manage_existing,
|
||||
volume,
|
||||
existing_ref)
|
||||
|
||||
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||
'get_unmanaged_volume_size',
|
||||
return_value=4)
|
||||
def test_manage_existing_get_size(self,
|
||||
mock_get_unmanaged_volume_size,
|
||||
mock_close_connection,
|
||||
mock_open_connection,
|
||||
mock_init):
|
||||
# Almost nothing to test here. Just that we call our function.
|
||||
volume = {'id': 'guid'}
|
||||
existing_ref = {'source-name': 'imavolumename'}
|
||||
res = self.driver.manage_existing_get_size(volume, existing_ref)
|
||||
mock_get_unmanaged_volume_size.assert_called_once_with(existing_ref)
|
||||
# The above is 4GB and change.
|
||||
self.assertEqual(4, res)
|
||||
|
||||
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||
'get_unmanaged_volume_size',
|
||||
return_value=4)
|
||||
def test_manage_existing_get_size_id(self,
|
||||
mock_get_unmanaged_volume_size,
|
||||
mock_close_connection,
|
||||
mock_open_connection,
|
||||
mock_init):
|
||||
# Almost nothing to test here. Just that we call our function.
|
||||
volume = {'id': 'guid'}
|
||||
existing_ref = {'source-id': 'imadeviceid'}
|
||||
res = self.driver.manage_existing_get_size(volume, existing_ref)
|
||||
mock_get_unmanaged_volume_size.assert_called_once_with(existing_ref)
|
||||
# The above is 4GB and change.
|
||||
self.assertEqual(4, res)
|
||||
|
||||
def test_manage_existing_get_size_bad_ref(self,
|
||||
mock_close_connection,
|
||||
mock_open_connection,
|
||||
mock_init):
|
||||
volume = {'id': 'guid'}
|
||||
existing_ref = {'banana-name': 'imavolumename'}
|
||||
self.assertRaises(exception.ManageExistingInvalidReference,
|
||||
self.driver.manage_existing_get_size,
|
||||
volume,
|
||||
existing_ref)
|
||||
|
||||
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||
'find_volume',
|
||||
return_value=VOLUME)
|
||||
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||
'unmanage')
|
||||
def test_unmanage(self,
|
||||
mock_unmanage,
|
||||
mock_find_volume,
|
||||
mock_close_connection,
|
||||
mock_open_connection,
|
||||
mock_init):
|
||||
volume = {'id': 'guid'}
|
||||
self.driver.unmanage(volume)
|
||||
mock_find_volume.assert_called_once_with(volume['id'])
|
||||
mock_unmanage.assert_called_once_with(self.VOLUME)
|
||||
|
||||
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||
'find_volume',
|
||||
return_value=None)
|
||||
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||
'unmanage')
|
||||
def test_unmanage_volume_not_found(self,
|
||||
mock_unmanage,
|
||||
mock_find_volume,
|
||||
mock_close_connection,
|
||||
mock_open_connection,
|
||||
mock_init):
|
||||
volume = {'id': 'guid'}
|
||||
self.driver.unmanage(volume)
|
||||
mock_find_volume.assert_called_once_with(volume['id'])
|
||||
self.assertFalse(mock_unmanage.called)
|
||||
|
@ -2099,7 +2099,7 @@ class DellSCSanAPITestCase(test.TestCase):
|
||||
mock_open_connection,
|
||||
mock_init):
|
||||
# Test case to find volume in the configured volume folder
|
||||
res = self.scapi._get_volume_list(self.volume_name, True)
|
||||
res = self.scapi._get_volume_list(self.volume_name, None, True)
|
||||
self.assertTrue(mock_post.called)
|
||||
self.assertTrue(mock_get_json.called)
|
||||
self.assertEqual(self.VOLUME_LIST, res, 'Unexpected volume list')
|
||||
@ -2117,17 +2117,17 @@ class DellSCSanAPITestCase(test.TestCase):
|
||||
mock_open_connection,
|
||||
mock_init):
|
||||
# Test case to find volume anywhere in the configured SC
|
||||
res = self.scapi._get_volume_list(self.volume_name, False)
|
||||
res = self.scapi._get_volume_list(self.volume_name, None, False)
|
||||
self.assertTrue(mock_post.called)
|
||||
self.assertTrue(mock_get_json.called)
|
||||
self.assertEqual(self.VOLUME_LIST, res, 'Unexpected volume list')
|
||||
|
||||
def test__get_volume_list_no_name(self,
|
||||
mock_close_connection,
|
||||
mock_open_connection,
|
||||
mock_init):
|
||||
# Test case specified volume name is None
|
||||
res = self.scapi._get_volume_list(None, True)
|
||||
def test_get_volume_list_no_name_no_id(self,
|
||||
mock_close_connection,
|
||||
mock_open_connection,
|
||||
mock_init):
|
||||
# Test case specified volume name is None and device id is None.
|
||||
res = self.scapi._get_volume_list(None, None, True)
|
||||
self.assertIsNone(res, 'None expected')
|
||||
|
||||
@mock.patch.object(dell_storagecenter_api.HttpClient,
|
||||
@ -2139,7 +2139,7 @@ class DellSCSanAPITestCase(test.TestCase):
|
||||
mock_open_connection,
|
||||
mock_init):
|
||||
# Test case to find volume in the configured volume folder
|
||||
res = self.scapi._get_volume_list(self.volume_name, True)
|
||||
res = self.scapi._get_volume_list(self.volume_name, None, True)
|
||||
self.assertTrue(mock_post.called)
|
||||
self.assertIsNone(res, 'None expected')
|
||||
|
||||
@ -4683,6 +4683,380 @@ class DellSCSanAPITestCase(test.TestCase):
|
||||
self.assertTrue(mock_find_replay.called)
|
||||
self.assertTrue(res)
|
||||
|
||||
def test_size_to_gb(self,
|
||||
mock_close_connection,
|
||||
mock_open_connection,
|
||||
mock_init):
|
||||
gb, rem = self.scapi._size_to_gb('1.073741824E9 Byte')
|
||||
self.assertEqual(1, gb)
|
||||
self.assertEqual(0, rem)
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self.scapi._size_to_gb,
|
||||
'banana')
|
||||
gb, rem = self.scapi._size_to_gb('1.073741924E9 Byte')
|
||||
self.assertEqual(1, gb)
|
||||
self.assertEqual(100, rem)
|
||||
|
||||
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||
'_get_volume_list',
|
||||
return_value=[{'configuredSize':
|
||||
'1.073741824E9 Bytes'}])
|
||||
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||
'_size_to_gb',
|
||||
return_value=(1, 0))
|
||||
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||
'_find_mappings',
|
||||
return_value=[])
|
||||
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||
'_find_volume_folder',
|
||||
return_value={'id': '1'})
|
||||
@mock.patch.object(dell_storagecenter_api.HttpClient,
|
||||
'put',
|
||||
return_value=RESPONSE_200)
|
||||
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||
'_get_id')
|
||||
def test_manage_existing(self,
|
||||
mock_get_id,
|
||||
mock_put,
|
||||
mock_find_volume_folder,
|
||||
mock_find_mappings,
|
||||
mock_size_to_gb,
|
||||
mock_get_volume_list,
|
||||
mock_close_connection,
|
||||
mock_open_connection,
|
||||
mock_init):
|
||||
newname = 'guid'
|
||||
existing = {'source-name': 'scvolname'}
|
||||
# First call is foldername, second is vollist. This is reflected
|
||||
# in the payload.
|
||||
mock_get_id.side_effect = ['1', '100']
|
||||
expected_url = 'StorageCenter/ScVolume/100'
|
||||
expected_payload = {'Name': newname,
|
||||
'VolumeFolder': '1'}
|
||||
self.scapi.manage_existing(newname, existing)
|
||||
mock_get_volume_list.asert_called_once_with(existing, False)
|
||||
self.assertTrue(mock_get_id.called)
|
||||
mock_put.assert_called_once_with(expected_url, expected_payload)
|
||||
self.assertTrue(mock_find_volume_folder.called)
|
||||
self.assertTrue(mock_find_mappings.called)
|
||||
self.assertTrue(mock_size_to_gb.called)
|
||||
|
||||
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||
'_get_volume_list',
|
||||
return_value=[{'configuredSize':
|
||||
'1.073741824E9 Bytes'}])
|
||||
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||
'_size_to_gb',
|
||||
return_value=(1, 0))
|
||||
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||
'_find_mappings',
|
||||
return_value=[])
|
||||
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||
'_find_volume_folder',
|
||||
return_value=None)
|
||||
@mock.patch.object(dell_storagecenter_api.HttpClient,
|
||||
'put',
|
||||
return_value=RESPONSE_200)
|
||||
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||
'_get_id',
|
||||
return_value='100')
|
||||
def test_manage_existing_folder_not_found(self,
|
||||
mock_get_id,
|
||||
mock_put,
|
||||
mock_find_volume_folder,
|
||||
mock_find_mappings,
|
||||
mock_size_to_gb,
|
||||
mock_get_volume_list,
|
||||
mock_close_connection,
|
||||
mock_open_connection,
|
||||
mock_init):
|
||||
# Same as above only we don't have a volume folder.
|
||||
newname = 'guid'
|
||||
existing = {'source-name': 'scvolname'}
|
||||
expected_url = 'StorageCenter/ScVolume/100'
|
||||
expected_payload = {'Name': newname}
|
||||
self.scapi.manage_existing(newname, existing)
|
||||
mock_get_volume_list.asert_called_once_with(
|
||||
existing.get('source-name'),
|
||||
existing.get('source-id'),
|
||||
False)
|
||||
mock_put.assert_called_once_with(expected_url, expected_payload)
|
||||
self.assertTrue(mock_get_id.called)
|
||||
self.assertTrue(mock_find_volume_folder.called)
|
||||
self.assertTrue(mock_find_mappings.called)
|
||||
self.assertTrue(mock_size_to_gb.called)
|
||||
|
||||
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||
'_get_volume_list',
|
||||
return_value=[])
|
||||
def test_manage_existing_vol_not_found(self,
|
||||
mock_get_volume_list,
|
||||
mock_close_connection,
|
||||
mock_open_connection,
|
||||
mock_init):
|
||||
|
||||
# Same as above only we don't have a volume folder.
|
||||
newname = 'guid'
|
||||
existing = {'source-name': 'scvolname'}
|
||||
self.assertRaises(exception.ManageExistingInvalidReference,
|
||||
self.scapi.manage_existing,
|
||||
newname,
|
||||
existing)
|
||||
mock_get_volume_list.asert_called_once_with(
|
||||
existing.get('source-name'),
|
||||
existing.get('source-id'),
|
||||
False)
|
||||
|
||||
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||
'_get_volume_list',
|
||||
return_value=[{}, {}, {}])
|
||||
def test_manage_existing_vol_multiple_found(self,
|
||||
mock_get_volume_list,
|
||||
mock_close_connection,
|
||||
mock_open_connection,
|
||||
mock_init):
|
||||
|
||||
# Same as above only we don't have a volume folder.
|
||||
newname = 'guid'
|
||||
existing = {'source-name': 'scvolname'}
|
||||
self.assertRaises(exception.ManageExistingInvalidReference,
|
||||
self.scapi.manage_existing,
|
||||
newname,
|
||||
existing)
|
||||
mock_get_volume_list.asert_called_once_with(
|
||||
existing.get('source-name'),
|
||||
existing.get('source-id'),
|
||||
False)
|
||||
|
||||
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||
'_get_volume_list',
|
||||
return_value=[{'configuredSize':
|
||||
'1.073741924E9 Bytes'}])
|
||||
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||
'_size_to_gb',
|
||||
return_value=(1, 100))
|
||||
def test_manage_existing_bad_size(self,
|
||||
mock_size_to_gb,
|
||||
mock_get_volume_list,
|
||||
mock_close_connection,
|
||||
mock_open_connection,
|
||||
mock_init):
|
||||
|
||||
# Same as above only we don't have a volume folder.
|
||||
newname = 'guid'
|
||||
existing = {'source-name': 'scvolname'}
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self.scapi.manage_existing,
|
||||
newname,
|
||||
existing)
|
||||
mock_get_volume_list.asert_called_once_with(
|
||||
existing.get('source-name'),
|
||||
existing.get('source-id'),
|
||||
False)
|
||||
self.assertTrue(mock_size_to_gb.called)
|
||||
|
||||
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||
'_get_volume_list',
|
||||
return_value=[{'configuredSize':
|
||||
'1.073741824E9 Bytes'}])
|
||||
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||
'_size_to_gb',
|
||||
return_value=(1, 0))
|
||||
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||
'_find_mappings',
|
||||
return_value=[{}, {}])
|
||||
def test_manage_existing_already_mapped(self,
|
||||
mock_find_mappings,
|
||||
mock_size_to_gb,
|
||||
mock_get_volume_list,
|
||||
mock_close_connection,
|
||||
mock_open_connection,
|
||||
mock_init):
|
||||
|
||||
newname = 'guid'
|
||||
existing = {'source-name': 'scvolname'}
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self.scapi.manage_existing,
|
||||
newname,
|
||||
existing)
|
||||
mock_get_volume_list.asert_called_once_with(
|
||||
existing.get('source-name'),
|
||||
existing.get('source-id'),
|
||||
False)
|
||||
self.assertTrue(mock_find_mappings.called)
|
||||
self.assertTrue(mock_size_to_gb.called)
|
||||
|
||||
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||
'_get_volume_list',
|
||||
return_value=[{'configuredSize':
|
||||
'1.073741824E9 Bytes'}])
|
||||
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||
'_size_to_gb',
|
||||
return_value=(1, 0))
|
||||
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||
'_find_mappings',
|
||||
return_value=[])
|
||||
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||
'_find_volume_folder',
|
||||
return_value=None)
|
||||
@mock.patch.object(dell_storagecenter_api.HttpClient,
|
||||
'put',
|
||||
return_value=RESPONSE_400)
|
||||
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||
'_get_id',
|
||||
return_value='100')
|
||||
def test_manage_existing_rename_fail(self,
|
||||
mock_get_id,
|
||||
mock_put,
|
||||
mock_find_volume_folder,
|
||||
mock_find_mappings,
|
||||
mock_size_to_gb,
|
||||
mock_get_volume_list,
|
||||
mock_close_connection,
|
||||
mock_open_connection,
|
||||
mock_init):
|
||||
# We fail on the _find_volume_folder to make this easier.
|
||||
newname = 'guid'
|
||||
existing = {'source-name': 'scvolname'}
|
||||
expected_url = 'StorageCenter/ScVolume/100'
|
||||
expected_payload = {'Name': newname}
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self.scapi.manage_existing,
|
||||
newname,
|
||||
existing)
|
||||
mock_get_volume_list.asert_called_once_with(
|
||||
existing.get('source-name'),
|
||||
existing.get('source-id'),
|
||||
False)
|
||||
self.assertTrue(mock_get_id.called)
|
||||
mock_put.assert_called_once_with(expected_url, expected_payload)
|
||||
self.assertTrue(mock_find_volume_folder.called)
|
||||
self.assertTrue(mock_find_mappings.called)
|
||||
self.assertTrue(mock_size_to_gb.called)
|
||||
|
||||
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||
'_get_volume_list',
|
||||
return_value=[{'configuredSize':
|
||||
'1.073741824E9 Bytes'}])
|
||||
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||
'_size_to_gb',
|
||||
return_value=(1, 0))
|
||||
def test_get_unmanaged_volume_size(self,
|
||||
mock_size_to_gb,
|
||||
mock_get_volume_list,
|
||||
mock_close_connection,
|
||||
mock_open_connection,
|
||||
mock_init):
|
||||
existing = {'source-name': 'scvolname'}
|
||||
res = self.scapi.get_unmanaged_volume_size(existing)
|
||||
mock_get_volume_list.asert_called_once_with(
|
||||
existing.get('source-name'),
|
||||
existing.get('source-id'),
|
||||
False)
|
||||
self.assertTrue(mock_size_to_gb.called)
|
||||
self.assertEqual(1, res)
|
||||
|
||||
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||
'_get_volume_list',
|
||||
return_value=[])
|
||||
def test_get_unmanaged_volume_size_not_found(self,
|
||||
mock_get_volume_list,
|
||||
mock_close_connection,
|
||||
mock_open_connection,
|
||||
mock_init):
|
||||
existing = {'source-name': 'scvolname'}
|
||||
self.assertRaises(exception.ManageExistingInvalidReference,
|
||||
self.scapi.get_unmanaged_volume_size,
|
||||
existing)
|
||||
mock_get_volume_list.asert_called_once_with(
|
||||
existing.get('source-name'),
|
||||
existing.get('source-id'),
|
||||
False)
|
||||
|
||||
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||
'_get_volume_list',
|
||||
return_value=[{}, {}, {}])
|
||||
def test_get_unmanaged_volume_size_many_found(self,
|
||||
mock_get_volume_list,
|
||||
mock_close_connection,
|
||||
mock_open_connection,
|
||||
mock_init):
|
||||
existing = {'source-name': 'scvolname'}
|
||||
self.assertRaises(exception.ManageExistingInvalidReference,
|
||||
self.scapi.get_unmanaged_volume_size,
|
||||
existing)
|
||||
mock_get_volume_list.asert_called_once_with(
|
||||
existing.get('source-name'),
|
||||
existing.get('source-id'),
|
||||
False)
|
||||
|
||||
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||
'_get_volume_list',
|
||||
return_value=[{'configuredSize':
|
||||
'1.073741924E9 Bytes'}])
|
||||
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||
'_size_to_gb',
|
||||
return_value=(1, 100))
|
||||
def test_get_unmanaged_volume_size_bad_size(self,
|
||||
mock_size_to_gb,
|
||||
mock_get_volume_list,
|
||||
mock_close_connection,
|
||||
mock_open_connection,
|
||||
mock_init):
|
||||
existing = {'source-name': 'scvolname'}
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self.scapi.get_unmanaged_volume_size,
|
||||
existing)
|
||||
self.assertTrue(mock_size_to_gb.called)
|
||||
mock_get_volume_list.asert_called_once_with(
|
||||
existing.get('source-name'),
|
||||
existing.get('source-id'),
|
||||
False)
|
||||
|
||||
@mock.patch.object(dell_storagecenter_api.HttpClient,
|
||||
'put',
|
||||
return_value=RESPONSE_200)
|
||||
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||
'_get_id',
|
||||
return_value='100')
|
||||
def test_unmanage(self,
|
||||
mock_get_id,
|
||||
mock_put,
|
||||
mock_close_connection,
|
||||
mock_open_connection,
|
||||
mock_init):
|
||||
# Same as above only we don't have a volume folder.
|
||||
scvolume = {'name': 'guid'}
|
||||
expected_url = 'StorageCenter/ScVolume/100'
|
||||
newname = 'Unmanaged_' + scvolume['name']
|
||||
expected_payload = {'Name': newname}
|
||||
self.scapi.unmanage(scvolume)
|
||||
self.assertTrue(mock_get_id.called)
|
||||
mock_put.assert_called_once_with(expected_url, expected_payload)
|
||||
|
||||
@mock.patch.object(dell_storagecenter_api.HttpClient,
|
||||
'put',
|
||||
return_value=RESPONSE_400)
|
||||
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
|
||||
'_get_id',
|
||||
return_value='100')
|
||||
def test_unmanage_fail(self,
|
||||
mock_get_id,
|
||||
mock_put,
|
||||
mock_close_connection,
|
||||
mock_open_connection,
|
||||
mock_init):
|
||||
# Same as above only we don't have a volume folder.
|
||||
scvolume = {'name': 'guid'}
|
||||
expected_url = 'StorageCenter/ScVolume/100'
|
||||
newname = 'Unmanaged_' + scvolume['name']
|
||||
expected_payload = {'Name': newname}
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self.scapi.unmanage,
|
||||
scvolume)
|
||||
self.assertTrue(mock_get_id.called)
|
||||
mock_put.assert_called_once_with(expected_url, expected_payload)
|
||||
|
||||
|
||||
class DellSCSanAPIConnectionTestCase(test.TestCase):
|
||||
|
||||
|
@ -169,7 +169,7 @@ class StorageCenterApi(object):
|
||||
Handles calls to Dell Enterprise Manager (EM) via the REST API interface.
|
||||
'''
|
||||
|
||||
APIVERSION = '1.2.0'
|
||||
APIVERSION = '2.0.1'
|
||||
|
||||
def __init__(self, host, port, user, password, verify):
|
||||
'''This creates a connection to Dell Enterprise Manager.
|
||||
@ -605,36 +605,39 @@ class StorageCenterApi(object):
|
||||
|
||||
return scvolume
|
||||
|
||||
def _get_volume_list(self, name, filterbyvfname=True):
|
||||
def _get_volume_list(self, name, deviceid, filterbyvfname=True):
|
||||
'''Return the specified list of volumes.
|
||||
|
||||
:param name: Volume name.
|
||||
:param deviceid: Volume device ID on the SC backend.
|
||||
:param filterbyvfname: If set to true then this filters by the preset
|
||||
folder name.
|
||||
:return: Returns the scvolume or None.
|
||||
:return: Returns the scvolume list or None.
|
||||
'''
|
||||
result = None
|
||||
pf = PayloadFilter()
|
||||
pf.append('scSerialNumber', self.ssn)
|
||||
# We need a name to find a volume.
|
||||
if name is not None:
|
||||
pf.append('Name', name)
|
||||
else:
|
||||
return None
|
||||
# set folderPath
|
||||
if filterbyvfname:
|
||||
vfname = (self.vfname if self.vfname.endswith('/')
|
||||
else self.vfname + '/')
|
||||
pf.append('volumeFolderPath', vfname)
|
||||
r = self.client.post('StorageCenter/ScVolume/GetList',
|
||||
pf.payload)
|
||||
if r.status_code != 200:
|
||||
LOG.debug('ScVolume GetList error %(name)s: %(code)d %(reason)s',
|
||||
{'name': name,
|
||||
'code': r.status_code,
|
||||
'reason': r.reason})
|
||||
else:
|
||||
result = self._get_json(r)
|
||||
# We need a name or a device ID to find a volume.
|
||||
if name or deviceid:
|
||||
pf = PayloadFilter()
|
||||
pf.append('scSerialNumber', self.ssn)
|
||||
if name is not None:
|
||||
pf.append('Name', name)
|
||||
if deviceid is not None:
|
||||
pf.append('DeviceId', deviceid)
|
||||
# set folderPath
|
||||
if filterbyvfname:
|
||||
vfname = (self.vfname if self.vfname.endswith('/')
|
||||
else self.vfname + '/')
|
||||
pf.append('volumeFolderPath', vfname)
|
||||
r = self.client.post('StorageCenter/ScVolume/GetList',
|
||||
pf.payload)
|
||||
if r.status_code != 200:
|
||||
LOG.debug('ScVolume GetList error '
|
||||
'%(name)s: %(code)d %(reason)s',
|
||||
{'name': name,
|
||||
'code': r.status_code,
|
||||
'reason': r.reason})
|
||||
else:
|
||||
result = self._get_json(r)
|
||||
# We return None if there was an error and a list if the command
|
||||
# succeeded. It might be an empty list.
|
||||
return result
|
||||
@ -661,6 +664,7 @@ class StorageCenterApi(object):
|
||||
|
||||
# Look for our volume in our folder.
|
||||
vollist = self._get_volume_list(name,
|
||||
None,
|
||||
True)
|
||||
# If an empty list was returned they probably moved the volumes or
|
||||
# changed the folder name so try again without the folder.
|
||||
@ -669,6 +673,7 @@ class StorageCenterApi(object):
|
||||
{'n': name,
|
||||
'v': self.vfname})
|
||||
vollist = self._get_volume_list(name,
|
||||
None,
|
||||
False)
|
||||
|
||||
# If multiple volumes of the same name are found we need to error.
|
||||
@ -1825,3 +1830,140 @@ class StorageCenterApi(object):
|
||||
return False
|
||||
# We either couldn't find it or expired it.
|
||||
return True
|
||||
|
||||
def _size_to_gb(self, spacestring):
|
||||
'''Splits a SC size string into GB and a remainder.
|
||||
|
||||
Space is returned in a string like ...
|
||||
7.38197504E8 Bytes
|
||||
Need to split that apart and convert to GB.
|
||||
|
||||
:param spacestring: SC size string.
|
||||
:return: Size in GB and remainder in byte.
|
||||
'''
|
||||
try:
|
||||
n = spacestring.split(' ', 1)
|
||||
fgb = int(float(n[0]) // 1073741824)
|
||||
frem = int(float(n[0]) % 1073741824)
|
||||
return fgb, frem
|
||||
|
||||
except Exception:
|
||||
# We received an invalid size string. Blow up.
|
||||
raise exception.VolumeBackendAPIException(
|
||||
_('Error retrieving volume size'))
|
||||
|
||||
def manage_existing(self, newname, existing):
|
||||
'''Finds the volume named existing and renames it.
|
||||
|
||||
This checks a few things. The volume has to exist. There can
|
||||
only be one volume by that name. Since cinder manages volumes
|
||||
by the GB it has to be defined on a GB boundry.
|
||||
|
||||
This renames existing to newname. newname is the guid from
|
||||
the cinder volume['id']. The volume is moved to the defined
|
||||
cinder volume folder.
|
||||
|
||||
:param newname: Name to rename the volume to.
|
||||
:param existing: The existing volume dict..
|
||||
:return: Nothing.
|
||||
:raises: VolumeBackendAPIException, ManageExistingInvalidReference
|
||||
'''
|
||||
vollist = self._get_volume_list(existing.get('source-name'),
|
||||
existing.get('source-id'),
|
||||
False)
|
||||
count = len(vollist)
|
||||
# If we found one volume with that name we can work with it.
|
||||
if count == 1:
|
||||
# First thing to check is if the size is something we can
|
||||
# work with.
|
||||
sz, rem = self._size_to_gb(vollist[0]['configuredSize'])
|
||||
if rem > 0:
|
||||
raise exception.VolumeBackendAPIException(
|
||||
_('Volume size must multiple of 1 GB.'))
|
||||
|
||||
# We only want to grab detached volumes.
|
||||
mappings = self._find_mappings(vollist[0])
|
||||
if len(mappings) > 0:
|
||||
raise exception.VolumeBackendAPIException(
|
||||
_('Volume is attached to a server. (%s)') % existing)
|
||||
|
||||
# Find our folder
|
||||
folder = self._find_volume_folder(True)
|
||||
|
||||
# If we actually have a place to put our volume create it
|
||||
if folder is None:
|
||||
LOG.warning(_LW('Unable to create folder %s'),
|
||||
self.vfname)
|
||||
|
||||
# Rename and move our volume.
|
||||
payload = {}
|
||||
payload['Name'] = newname
|
||||
if folder:
|
||||
payload['VolumeFolder'] = self._get_id(folder)
|
||||
|
||||
r = self.client.put('StorageCenter/ScVolume/%s' %
|
||||
self._get_id(vollist[0]),
|
||||
payload)
|
||||
if r.status_code != 200:
|
||||
LOG.error(_LE('ScVolume error on rename: %(code)d %(reason)s'),
|
||||
{'code': r.status_code,
|
||||
'reason': r.reason})
|
||||
raise exception.VolumeBackendAPIException(
|
||||
_('Unable to manage volume %s') % existing)
|
||||
elif count > 1:
|
||||
raise exception.ManageExistingInvalidReference(
|
||||
_('Volume not unique. (%s)') % existing)
|
||||
else:
|
||||
raise exception.ManageExistingInvalidReference(
|
||||
_('Volume not found. (%s)') % existing)
|
||||
|
||||
def get_unmanaged_volume_size(self, existing):
|
||||
'''Looks up the volume named existing and returns its size string.
|
||||
|
||||
:param existing: Existing volume dict.
|
||||
:return: The SC configuredSize string.
|
||||
:raises: ManageExistingInvalidReference
|
||||
'''
|
||||
vollist = self._get_volume_list(existing.get('source-name'),
|
||||
existing.get('source-id'),
|
||||
False)
|
||||
count = len(vollist)
|
||||
# If we found one volume with that name we can work with it.
|
||||
if count == 1:
|
||||
sz, rem = self._size_to_gb(vollist[0]['configuredSize'])
|
||||
if rem > 0:
|
||||
raise exception.VolumeBackendAPIException(
|
||||
_('Volume size must multiple of 1 GB.'))
|
||||
return sz
|
||||
elif count > 1:
|
||||
raise exception.ManageExistingInvalidReference(
|
||||
_('Volume not unique. (%s)') % existing)
|
||||
else:
|
||||
raise exception.ManageExistingInvalidReference(
|
||||
_('Volume not found. (%s)') % existing)
|
||||
|
||||
def unmanage(self, scvolume):
|
||||
'''Unmanage our volume.
|
||||
|
||||
We simply rename with with a prefix of 'Unmanaged_'. That's it.
|
||||
|
||||
:param scvolume: The Dell SC volume object.
|
||||
:return: Nothing.
|
||||
:raises: VolumeBackendAPIException
|
||||
'''
|
||||
newname = 'Unmanaged_' + scvolume['name']
|
||||
payload = {}
|
||||
payload['Name'] = newname
|
||||
r = self.client.put('StorageCenter/ScVolume/%s' %
|
||||
self._get_id(scvolume),
|
||||
payload)
|
||||
if r.status_code == 200:
|
||||
LOG.info(_LI('Volume %s unmanaged.'), scvolume['name'])
|
||||
else:
|
||||
LOG.error(_LE('ScVolume error on rename: %(code)d %(reason)s'),
|
||||
{'code': r.status_code,
|
||||
'reason': r.reason})
|
||||
raise exception.VolumeBackendAPIException(
|
||||
_('Unable to rename volume %(existing)s to %(newname)s') %
|
||||
{'existing': scvolume['name'],
|
||||
'newname': newname})
|
||||
|
@ -48,7 +48,9 @@ CONF = cfg.CONF
|
||||
CONF.register_opts(common_opts)
|
||||
|
||||
|
||||
class DellCommonDriver(driver.VolumeDriver):
|
||||
class DellCommonDriver(driver.ConsistencyGroupVD, driver.ManageableVD,
|
||||
driver.ExtendVD, driver.CloneableVD, driver.SnapshotVD,
|
||||
driver.BaseVD):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(DellCommonDriver, self).__init__(*args, **kwargs)
|
||||
@ -374,6 +376,7 @@ class DellCommonDriver(driver.VolumeDriver):
|
||||
# The world was horrible to us so we should error and leave.
|
||||
LOG.error(_LE('Unable to rename the logical volume for volume: %s'),
|
||||
original_volume_name)
|
||||
|
||||
return {'_name_id': new_volume['_name_id'] or new_volume['id']}
|
||||
|
||||
def create_consistencygroup(self, context, group):
|
||||
@ -530,3 +533,77 @@ class DellCommonDriver(driver.VolumeDriver):
|
||||
model_update = {'status': 'deleted'}
|
||||
|
||||
return model_update, snapshots
|
||||
|
||||
def manage_existing(self, volume, existing_ref):
|
||||
"""Brings an existing backend storage object under Cinder management.
|
||||
|
||||
existing_ref is passed straight through from the API request's
|
||||
manage_existing_ref value, and it is up to the driver how this should
|
||||
be interpreted. It should be sufficient to identify a storage object
|
||||
that the driver should somehow associate with the newly-created cinder
|
||||
volume structure.
|
||||
|
||||
There are two ways to do this:
|
||||
|
||||
1. Rename the backend storage object so that it matches the,
|
||||
volume['name'] which is how drivers traditionally map between a
|
||||
cinder volume and the associated backend storage object.
|
||||
|
||||
2. Place some metadata on the volume, or somewhere in the backend, that
|
||||
allows other driver requests (e.g. delete, clone, attach, detach...)
|
||||
to locate the backend storage object when required.
|
||||
|
||||
If the existing_ref doesn't make sense, or doesn't refer to an existing
|
||||
backend storage object, raise a ManageExistingInvalidReference
|
||||
exception.
|
||||
|
||||
The volume may have a volume_type, and the driver can inspect that and
|
||||
compare against the properties of the referenced backend storage
|
||||
object. If they are incompatible, raise a
|
||||
ManageExistingVolumeTypeMismatch, specifying a reason for the failure.
|
||||
|
||||
:param volume: Cinder volume to manage
|
||||
:param existing_ref: Driver-specific information used to identify a
|
||||
volume
|
||||
"""
|
||||
if existing_ref.get('source-name') or existing_ref.get('source-id'):
|
||||
with self._client.open_connection() as api:
|
||||
api.manage_existing(volume['id'], existing_ref)
|
||||
else:
|
||||
raise exception.ManageExistingInvalidReference(
|
||||
_('Must specify source-name or source-id. (%s)') %
|
||||
existing_ref)
|
||||
|
||||
def manage_existing_get_size(self, volume, existing_ref):
|
||||
"""Return size of volume to be managed by manage_existing.
|
||||
|
||||
When calculating the size, round up to the next GB.
|
||||
|
||||
:param volume: Cinder volume to manage
|
||||
:param existing_ref: Driver-specific information used to identify a
|
||||
volume
|
||||
"""
|
||||
if existing_ref.get('source-name') or existing_ref.get('source-id'):
|
||||
with self._client.open_connection() as api:
|
||||
return api.get_unmanaged_volume_size(existing_ref)
|
||||
else:
|
||||
raise exception.ManageExistingInvalidReference(
|
||||
_('Must specify source-name or source-id. (%s)') %
|
||||
existing_ref)
|
||||
|
||||
def unmanage(self, volume):
|
||||
"""Removes the specified volume from Cinder management.
|
||||
|
||||
Does not delete the underlying backend storage object.
|
||||
|
||||
For most drivers, this will not need to do anything. However, some
|
||||
drivers might use this call as an opportunity to clean up any
|
||||
Cinder-specific configuration that they have associated with the
|
||||
backend storage object.
|
||||
|
||||
:param volume: Cinder volume to unmanage
|
||||
"""
|
||||
with self._client.open_connection() as api:
|
||||
scvolume = api.find_volume(volume['id'])
|
||||
if scvolume:
|
||||
api.unmanage(scvolume)
|
||||
|
@ -38,9 +38,12 @@ class DellStorageCenterFCDriver(dell_storagecenter_common.DellCommonDriver,
|
||||
1.0.0 - Initial driver
|
||||
1.1.0 - Added extra spec support for Storage Profile selection
|
||||
1.2.0 - Added consistency group support.
|
||||
2.0.0 - Switched to inheriting functional objects rather than volume
|
||||
driver.
|
||||
2.1.0 - Added support for ManageableVD.
|
||||
'''
|
||||
|
||||
VERSION = '1.2.0'
|
||||
VERSION = '2.1.0'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(DellStorageCenterFCDriver, self).__init__(*args, **kwargs)
|
||||
|
@ -36,9 +36,12 @@ class DellStorageCenterISCSIDriver(dell_storagecenter_common.DellCommonDriver,
|
||||
1.0.0 - Initial driver
|
||||
1.1.0 - Added extra spec support for Storage Profile selection
|
||||
1.2.0 - Added consistency group support.
|
||||
2.0.0 - Switched to inheriting functional objects rather than volume
|
||||
driver.
|
||||
2.1.0 - Added support for ManageableVD.
|
||||
'''
|
||||
|
||||
VERSION = '1.2.0'
|
||||
VERSION = '2.1.0'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(DellStorageCenterISCSIDriver, self).__init__(*args, **kwargs)
|
||||
|
Loading…
Reference in New Issue
Block a user