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)
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')
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,
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')
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):
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.
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
# 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.
{'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.
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})
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)
# 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):
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)
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)
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)