]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Dell SC: Enable use of Storage Profiles
authorSean McGinnis <sean_mcginnis@dell.com>
Thu, 18 Jun 2015 21:56:33 +0000 (16:56 -0500)
committerSean McGinnis <sean_mcginnis@dell.com>
Mon, 22 Jun 2015 20:49:06 +0000 (15:49 -0500)
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
cinder/tests/unit/test_dellscapi.py
cinder/volume/drivers/dell/dell_storagecenter_api.py
cinder/volume/drivers/dell/dell_storagecenter_common.py
cinder/volume/drivers/dell/dell_storagecenter_fc.py
cinder/volume/drivers/dell/dell_storagecenter_iscsi.py

index efb4f020174da082f86d6c7eb42e49a15a6ac9d6..e8bb59a06d89727c89123e0f5588fcf25bd068b6 100644 (file)
@@ -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',
index 79319d454b371e0ac753b7a13bd459428dc4c284..b243a70869a16d7f20f02e09bdd15d9c1d7511c9 100644 (file)
@@ -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)
index 4e190516b7daf9721e9a7e96dbf8d5a807f1740a..bfa06c1246abd1443bc81b3398eb60ab6a62a73a 100644 (file)
@@ -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:
index f6acb95b1eaa0111e4d4430dab379b6c4502482d..b259adba8e566d8680c22fe652bf9b60de9934eb 100644 (file)
@@ -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'),
index 2bb3f5a6f85ddfb6ec2c17d188cc3d80bc240ed1..32b0465b7b36121a7583ca39c3e106ffa82f08f7 100644 (file)
@@ -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)
index 32d63248a6f688ae43b2f7fc66ebc5d815676814..92faa037885ef032fbda1ae181b8c22b4d68c6d6 100644 (file)
@@ -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)