From 5ff07f9577c7b971c86759e3797123435dbc6aec Mon Sep 17 00:00:00 2001 From: Sean McGinnis Date: Thu, 18 Jun 2015 16:56:33 -0500 Subject: [PATCH] Dell SC: Enable use of Storage Profiles The Storage Center array allows volumes to be associated with different Storage Profiles. These profiles allow setting tiering and RAID types per volume. The driver currently only uses the default Storage Profile configured for the Cinder user account. This patch adds the capability to use different Storage Profiles by using extra specs to allow the creation of different volume types for the desired profiles. DocImpact Change-Id: Icf76fceca5a0ae20bb08b276b0c41ef6cdb31087 --- cinder/tests/unit/test_dellsc.py | 27 ++- cinder/tests/unit/test_dellscapi.py | 173 ++++++++++++++++++ .../drivers/dell/dell_storagecenter_api.py | 50 ++++- .../drivers/dell/dell_storagecenter_common.py | 17 +- .../drivers/dell/dell_storagecenter_fc.py | 6 +- .../drivers/dell/dell_storagecenter_iscsi.py | 6 +- 6 files changed, 272 insertions(+), 7 deletions(-) diff --git a/cinder/tests/unit/test_dellsc.py b/cinder/tests/unit/test_dellsc.py index efb4f020174..e8bb59a06d8 100644 --- a/cinder/tests/unit/test_dellsc.py +++ b/cinder/tests/unit/test_dellsc.py @@ -19,6 +19,7 @@ from cinder import exception from cinder import test from cinder.volume.drivers.dell import dell_storagecenter_api from cinder.volume.drivers.dell import dell_storagecenter_iscsi +from cinder.volume import volume_types import mock @@ -273,7 +274,31 @@ class DellSCSanISCSIDriverTestCase(test.TestCase): volume = {'id': self.volume_name, 'size': 1} self.driver.create_volume(volume) mock_create_volume.assert_called_once_with(self.volume_name, - 1) + 1, + None) + + @mock.patch.object(dell_storagecenter_api.StorageCenterApi, + 'create_volume', + return_value=VOLUME) + @mock.patch.object(dell_storagecenter_api.StorageCenterApi, + 'find_sc', + return_value=12345) + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'storagetype:storageprofile': 'HighPriority'}) + def test_create_volume_storage_profile(self, + mock_extra, + mock_find_sc, + mock_create_volume, + mock_close_connection, + mock_open_connection, + mock_init): + volume = {'id': self.volume_name, 'size': 1, 'volume_type_id': 'abc'} + self.driver.create_volume(volume) + mock_create_volume.assert_called_once_with(self.volume_name, + 1, + "HighPriority") @mock.patch.object(dell_storagecenter_api.StorageCenterApi, 'create_volume', diff --git a/cinder/tests/unit/test_dellscapi.py b/cinder/tests/unit/test_dellscapi.py index 79319d454b3..b243a70869a 100644 --- a/cinder/tests/unit/test_dellscapi.py +++ b/cinder/tests/unit/test_dellscapi.py @@ -12,6 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. +import ddt from oslo_log import log as logging from cinder import context @@ -30,6 +31,7 @@ LOG = logging.getLogger(__name__) # from trying to contact a Dell Storage Center. +@ddt.ddt @mock.patch.object(dell_storagecenter_api.StorageCenterApi, '__init__', return_value=None) @@ -1389,6 +1391,80 @@ class DellSCSanAPITestCase(test.TestCase): u'storageAlertThreshold': 10, u'objectType': u'StorageCenterStorageUsage'} + STORAGE_PROFILE_LIST = [ + {u'allowedForFlashOptimized': False, + u'allowedForNonFlashOptimized': True, + u'index': 1, + u'instanceId': u'64158.1', + u'instanceName': u'Recommended', + u'name': u'Recommended', + u'notes': u'', + u'objectType': u'ScStorageProfile', + u'raidTypeDescription': u'RAID 10 Active, RAID 5 or RAID 6 Replay', + u'raidTypeUsed': u'Mixed', + u'scName': u'Storage Center 64158', + u'scSerialNumber': 64158, + u'tiersUsedDescription': u'Tier 1, Tier 2, Tier 3', + u'useTier1Storage': True, + u'useTier2Storage': True, + u'useTier3Storage': True, + u'userCreated': False, + u'volumeCount': 125}, + {u'allowedForFlashOptimized': False, + u'allowedForNonFlashOptimized': True, + u'index': 2, + u'instanceId': u'64158.2', + u'instanceName': u'High Priority', + u'name': u'High Priority', + u'notes': u'', + u'objectType': u'ScStorageProfile', + u'raidTypeDescription': u'RAID 10 Active, RAID 5 or RAID 6 Replay', + u'raidTypeUsed': u'Mixed', + u'scName': u'Storage Center 64158', + u'scSerialNumber': 64158, + u'tiersUsedDescription': u'Tier 1', + u'useTier1Storage': True, + u'useTier2Storage': False, + u'useTier3Storage': False, + u'userCreated': False, + u'volumeCount': 0}, + {u'allowedForFlashOptimized': False, + u'allowedForNonFlashOptimized': True, + u'index': 3, + u'instanceId': u'64158.3', + u'instanceName': u'Medium Priority', + u'name': u'Medium Priority', + u'notes': u'', + u'objectType': u'ScStorageProfile', + u'raidTypeDescription': u'RAID 10 Active, RAID 5 or RAID 6 Replay', + u'raidTypeUsed': u'Mixed', + u'scName': u'Storage Center 64158', + u'scSerialNumber': 64158, + u'tiersUsedDescription': u'Tier 2', + u'useTier1Storage': False, + u'useTier2Storage': True, + u'useTier3Storage': False, + u'userCreated': False, + u'volumeCount': 0}, + {u'allowedForFlashOptimized': True, + u'allowedForNonFlashOptimized': True, + u'index': 4, + u'instanceId': u'64158.4', + u'instanceName': u'Low Priority', + u'name': u'Low Priority', + u'notes': u'', + u'objectType': u'ScStorageProfile', + u'raidTypeDescription': u'RAID 10 Active, RAID 5 or RAID 6 Replay', + u'raidTypeUsed': u'Mixed', + u'scName': u'Storage Center 64158', + u'scSerialNumber': 64158, + u'tiersUsedDescription': u'Tier 3', + u'useTier1Storage': False, + u'useTier2Storage': False, + u'useTier3Storage': True, + u'userCreated': False, + u'volumeCount': 0}] + IQN = 'iqn.2002-03.com.compellent:5000D31000000001' WWN = u'21000024FF30441C' @@ -1712,6 +1788,57 @@ class DellSCSanAPITestCase(test.TestCase): self.configuration.dell_sc_volume_folder) self.assertEqual(self.FLDR, res, 'Unexpected Folder') + @mock.patch.object(dell_storagecenter_api.StorageCenterApi, + '_get_json', + return_value=STORAGE_PROFILE_LIST) + @mock.patch.object(dell_storagecenter_api.HttpClient, + 'post', + return_value=RESPONSE_200) + def test_find_storage_profile_fail(self, + mock_json, + mock_find_folder, + mock_close_connection, + mock_open_connection, + mock_init): + # Test case where _find_volume_folder returns none + res = self.scapi._find_storage_profile("Blah") + self.assertIsNone(res) + + @mock.patch.object(dell_storagecenter_api.StorageCenterApi, + '_get_json', + return_value=STORAGE_PROFILE_LIST) + @mock.patch.object(dell_storagecenter_api.HttpClient, + 'post', + return_value=RESPONSE_200) + def test_find_storage_profile_none(self, + mock_json, + mock_find_folder, + mock_close_connection, + mock_open_connection, + mock_init): + # Test case where _find_storage_profile returns none + res = self.scapi._find_storage_profile(None) + self.assertIsNone(res) + + @mock.patch.object(dell_storagecenter_api.StorageCenterApi, + '_get_json', + return_value=STORAGE_PROFILE_LIST) + @mock.patch.object(dell_storagecenter_api.HttpClient, + 'post', + return_value=RESPONSE_200) + @ddt.data('HighPriority', 'highpriority', 'High Priority') + def test_find_storage_profile(self, + value, + mock_json, + mock_find_folder, + mock_close_connection, + mock_open_connection, + mock_init): + res = self.scapi._find_storage_profile(value) + self.assertIsNotNone(res, 'Expected matching storage profile!') + self.assertEqual(self.STORAGE_PROFILE_LIST[1]['instanceId'], + res.get('instanceId')) + @mock.patch.object(dell_storagecenter_api.StorageCenterApi, '_create_folder_path', return_value=FLDR) @@ -1819,6 +1946,52 @@ class DellSCSanAPITestCase(test.TestCase): mock_find_volume_folder.assert_called_once_with(True) self.assertEqual(self.VOLUME, res, 'Unexpected ScVolume') + @mock.patch.object(dell_storagecenter_api.StorageCenterApi, + '_find_storage_profile', + return_value=None) + @mock.patch.object(dell_storagecenter_api.StorageCenterApi, + '_find_volume_folder', + return_value=FLDR) + def test_create_volume_storage_profile_missing(self, + mock_find_volume_folder, + mock_find_storage_profile, + mock_close_connection, + mock_open_connection, + mock_init): + self.assertRaises(exception.VolumeBackendAPIException, + self.scapi.create_volume, + self.volume_name, + 1, + 'Blah') + + @mock.patch.object(dell_storagecenter_api.StorageCenterApi, + '_get_json', + return_value=VOLUME) + @mock.patch.object(dell_storagecenter_api.StorageCenterApi, + '_find_storage_profile', + return_value=STORAGE_PROFILE_LIST[0]) + @mock.patch.object(dell_storagecenter_api.StorageCenterApi, + '_find_volume_folder', + return_value=FLDR) + @mock.patch.object(dell_storagecenter_api.HttpClient, + 'post', + return_value=RESPONSE_201) + def test_create_volume_storage_profile(self, + mock_post, + mock_find_volume_folder, + mock_find_storage_profile, + mock_get_json, + mock_close_connection, + mock_open_connection, + mock_init): + self.scapi.create_volume( + self.volume_name, + 1, + 'Recommended') + actual = mock_post.call_args[0][1]['StorageProfile'] + expected = self.STORAGE_PROFILE_LIST[0]['instanceId'] + self.assertEqual(expected, actual) + @mock.patch.object(dell_storagecenter_api.StorageCenterApi, 'find_volume', return_value=VOLUME) diff --git a/cinder/volume/drivers/dell/dell_storagecenter_api.py b/cinder/volume/drivers/dell/dell_storagecenter_api.py index 4e190516b7d..bfa06c1246a 100644 --- a/cinder/volume/drivers/dell/dell_storagecenter_api.py +++ b/cinder/volume/drivers/dell/dell_storagecenter_api.py @@ -502,7 +502,38 @@ class StorageCenterApi(object): LOG.warning(_LW('Volume initialization failure. (%s)'), self._get_id(scvolume)) - def create_volume(self, name, size): + def _find_storage_profile(self, storage_profile): + '''Looks for a Storage Profile on the array. + + Storage Profiles determine tiering settings. If not specified a volume + will use the Default storage profile. + + :param storage_profile: The Storage Profile name to find with any + spaces stripped. + :returns: The Storage Profile object or None. + ''' + if not storage_profile: + return None + + # Since we are stripping out spaces for convenience we are not + # able to just filter on name. Need to get all Storage Profiles + # and look through for the one we want. Never many profiles, so + # this doesn't cause as much overhead as it might seem. + storage_profile = storage_profile.replace(' ', '').lower() + pf = PayloadFilter() + pf.append('scSerialNumber', self.ssn, 'Equals') + r = self.client.post( + 'StorageCenter/ScStorageProfile/GetList', pf.payload) + if r.status_code == 200: + profiles = self._get_json(r) + for profile in profiles: + # Look for the stripped, case insensitive match + name = profile.get('name', '').replace(' ', '').lower() + if name == storage_profile: + return profile + return None + + def create_volume(self, name, size, storage_profile=None): '''Creates a new volume on the Storage Center. It will create it in a folder called self.vfname. If self.vfname @@ -511,12 +542,16 @@ class StorageCenterApi(object): :param name: Name of the volume to be created on the Dell SC backend. This is the cinder volume ID. + :param size: The size of the volume to be created in GB. + :param storage_profile: Optional storage profile to set for the volume. :returns: Dell Volume object or None. ''' - LOG.debug('Create Volume %(name)s %(ssn)s %(folder)s', + LOG.debug('Create Volume %(name)s %(ssn)s %(folder)s %(profile)s', {'name': name, 'ssn': self.ssn, - 'folder': self.vfname}) + 'folder': self.vfname, + 'profile': storage_profile, + }) # Find our folder folder = self._find_volume_folder(True) @@ -526,6 +561,13 @@ class StorageCenterApi(object): LOG.warning(_LW('Unable to create folder %s'), self.vfname) + # See if we need a storage profile + profile = self._find_storage_profile(storage_profile) + if storage_profile and profile is None: + msg = _('Storage Profile %s not found.') % storage_profile + raise exception.VolumeBackendAPIException( + data=msg) + # Init our return. scvolume = None @@ -537,6 +579,8 @@ class StorageCenterApi(object): payload['StorageCenter'] = self.ssn if folder is not None: payload['VolumeFolder'] = self._get_id(folder) + if profile: + payload['StorageProfile'] = self._get_id(profile) r = self.client.post('StorageCenter/ScVolume', payload) if r.status_code == 201: diff --git a/cinder/volume/drivers/dell/dell_storagecenter_common.py b/cinder/volume/drivers/dell/dell_storagecenter_common.py index f6acb95b1ea..b259adba8e5 100644 --- a/cinder/volume/drivers/dell/dell_storagecenter_common.py +++ b/cinder/volume/drivers/dell/dell_storagecenter_common.py @@ -20,6 +20,7 @@ from cinder import exception from cinder.i18n import _, _LE, _LW from cinder.volume.drivers.dell import dell_storagecenter_api from cinder.volume.drivers.san import san +from cinder.volume import volume_types common_opts = [ @@ -86,12 +87,25 @@ class DellCommonDriver(san.SanDriver): with self._client.open_connection() as api: api.find_sc() + def _get_volume_extra_specs(self, volume): + '''Gets extra specs for the given volume.''' + type_id = volume.get('volume_type_id') + if type_id: + return volume_types.get_volume_type_extra_specs(type_id) + + return {} + def create_volume(self, volume): '''Create a volume.''' # We use id as our name as it is unique. volume_name = volume.get('id') volume_size = volume.get('size') + + # See if we have any extra specs. + specs = self._get_volume_extra_specs(volume) + storage_profile = specs.get('storagetype:storageprofile') + LOG.debug('Creating volume %(name)s of size %(size)s', {'name': volume_name, 'size': volume_size}) @@ -100,7 +114,8 @@ class DellCommonDriver(san.SanDriver): try: if api.find_sc(): scvolume = api.create_volume(volume_name, - volume_size) + volume_size, + storage_profile) except Exception: with excutils.save_and_reraise_exception(): LOG.error(_LE('Failed to create volume %s'), diff --git a/cinder/volume/drivers/dell/dell_storagecenter_fc.py b/cinder/volume/drivers/dell/dell_storagecenter_fc.py index 2bb3f5a6f85..32b0465b7b3 100644 --- a/cinder/volume/drivers/dell/dell_storagecenter_fc.py +++ b/cinder/volume/drivers/dell/dell_storagecenter_fc.py @@ -33,9 +33,13 @@ class DellStorageCenterFCDriver(dell_storagecenter_common.DellCommonDriver, To enable the driver add the following line to the cinder configuration: volume_driver=cinder.volume.drivers.dell.DellStorageCenterFCDriver + + Version history: + 1.0.0 - Initial driver + 1.1.0 - Added extra spec support for Storage Profile selection ''' - VERSION = '1.0.2' + VERSION = '1.1.0' def __init__(self, *args, **kwargs): super(DellStorageCenterFCDriver, self).__init__(*args, **kwargs) diff --git a/cinder/volume/drivers/dell/dell_storagecenter_iscsi.py b/cinder/volume/drivers/dell/dell_storagecenter_iscsi.py index 32d63248a6f..92faa037885 100644 --- a/cinder/volume/drivers/dell/dell_storagecenter_iscsi.py +++ b/cinder/volume/drivers/dell/dell_storagecenter_iscsi.py @@ -32,9 +32,13 @@ class DellStorageCenterISCSIDriver(san.SanISCSIDriver, To enable the driver add the following line to the cinder configuration: volume_driver=cinder.volume.drivers.dell.DellStorageCenterISCSIDriver + + Version history: + 1.0.0 - Initial driver + 1.1.0 - Added extra spec support for Storage Profile selection ''' - VERSION = '1.0.2' + VERSION = '1.1.0' def __init__(self, *args, **kwargs): super(DellStorageCenterISCSIDriver, self).__init__(*args, **kwargs)