Merge "Dell SC: Enable use of Storage Profiles"
This commit is contained in:
commit
27b37f9034
|
@ -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',
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue