Merge "VMAX Driver - QoS support for the VMAX3"
This commit is contained in:
commit
be7c9d4a44
@ -1164,6 +1164,14 @@ class FakeEcomConnection(object):
|
|||||||
else:
|
else:
|
||||||
targetmaskinggroup['ElementName'] = (
|
targetmaskinggroup['ElementName'] = (
|
||||||
self.data.storagegroupname)
|
self.data.storagegroupname)
|
||||||
|
if 'EMCMaximumIO' in objectpath:
|
||||||
|
targetmaskinggroup['EMCMaximumIO'] = objectpath['EMCMaximumIO']
|
||||||
|
if 'EMCMaximumBandwidth' in objectpath:
|
||||||
|
targetmaskinggroup['EMCMaximumBandwidth'] = (
|
||||||
|
objectpath['EMCMaximumBandwidth'])
|
||||||
|
if 'EMCMaxIODynamicDistributionType' in objectpath:
|
||||||
|
targetmaskinggroup['EMCMaxIODynamicDistributionType'] = (
|
||||||
|
objectpath['EMCMaxIODynamicDistributionType'])
|
||||||
return targetmaskinggroup
|
return targetmaskinggroup
|
||||||
|
|
||||||
def _getinstance_unit(self, objectpath):
|
def _getinstance_unit(self, objectpath):
|
||||||
@ -6242,8 +6250,8 @@ class EMCV3DriverTestCase(test.TestCase):
|
|||||||
'get_volume_type_extra_specs',
|
'get_volume_type_extra_specs',
|
||||||
return_value={'volume_backend_name': 'V3_BE'})
|
return_value={'volume_backend_name': 'V3_BE'})
|
||||||
def test_create_cgsnapshot_v3_success(
|
def test_create_cgsnapshot_v3_success(
|
||||||
self, _mock_volume_type, _mock_storage, _mock_cg, _mock_members,
|
self, _mock_volume_type, _mock_storage, _mock_cg,
|
||||||
mock_rg):
|
_mock_members, mock_rg):
|
||||||
provisionv3 = self.driver.common.provisionv3
|
provisionv3 = self.driver.common.provisionv3
|
||||||
provisionv3.create_group_replica = mock.Mock(return_value=(0, None))
|
provisionv3.create_group_replica = mock.Mock(return_value=(0, None))
|
||||||
self.driver.create_cgsnapshot(
|
self.driver.create_cgsnapshot(
|
||||||
@ -6448,6 +6456,9 @@ class EMCV3DriverTestCase(test.TestCase):
|
|||||||
targetInstance = (
|
targetInstance = (
|
||||||
conn.EnumerateInstanceNames("EMC_StorageVolume")[0])
|
conn.EnumerateInstanceNames("EMC_StorageVolume")[0])
|
||||||
deviceID = targetInstance['DeviceID']
|
deviceID = targetInstance['DeviceID']
|
||||||
|
common._delete_from_pool_v3(storageConfigService, targetInstance,
|
||||||
|
targetInstance['Name'], deviceID,
|
||||||
|
extraSpecs)
|
||||||
common._delete_from_pool_v3.assert_called_with(storageConfigService,
|
common._delete_from_pool_v3.assert_called_with(storageConfigService,
|
||||||
targetInstance,
|
targetInstance,
|
||||||
targetInstance['Name'],
|
targetInstance['Name'],
|
||||||
@ -7162,7 +7173,6 @@ class EMCV2MultiPoolDriverMultipleEcomsTestCase(test.TestCase):
|
|||||||
self.fake_sleep)
|
self.fake_sleep)
|
||||||
self.stubs.Set(emc_vmax_utils.EMCVMAXUtils, 'isArrayV3',
|
self.stubs.Set(emc_vmax_utils.EMCVMAXUtils, 'isArrayV3',
|
||||||
self.fake_is_v3)
|
self.fake_is_v3)
|
||||||
|
|
||||||
driver = emc_vmax_fc.EMCVMAXFCDriver(configuration=configuration)
|
driver = emc_vmax_fc.EMCVMAXFCDriver(configuration=configuration)
|
||||||
driver.db = FakeDB()
|
driver.db = FakeDB()
|
||||||
driver.common.conn = FakeEcomConnection()
|
driver.common.conn = FakeEcomConnection()
|
||||||
@ -8001,6 +8011,51 @@ class EMCVMAXUtilsTest(test.TestCase):
|
|||||||
self.driver.utils.get_ratio_from_max_sub_per(str(0)))
|
self.driver.utils.get_ratio_from_max_sub_per(str(0)))
|
||||||
self.assertIsNone(max_subscription_percent_float)
|
self.assertIsNone(max_subscription_percent_float)
|
||||||
|
|
||||||
|
def test_update_storage_QOS(self):
|
||||||
|
conn = FakeEcomConnection()
|
||||||
|
pywbem = mock.Mock()
|
||||||
|
pywbem.cim_obj = mock.Mock()
|
||||||
|
pywbem.cim_obj.CIMInstance = mock.Mock()
|
||||||
|
emc_vmax_utils.pywbem = pywbem
|
||||||
|
|
||||||
|
extraSpecs = {'volume_backend_name': 'V3_BE',
|
||||||
|
'qos': {
|
||||||
|
'maxIOPS': '6000',
|
||||||
|
'maxMBPS': '6000',
|
||||||
|
'DistributionType': 'Always'
|
||||||
|
}}
|
||||||
|
|
||||||
|
storageGroupInstanceName = {
|
||||||
|
'CreationClassName': 'CIM_DeviceMaskingGroup',
|
||||||
|
'EMCMaximumIO': 6000,
|
||||||
|
'EMCMaximumBandwidth': 5000,
|
||||||
|
'EMCMaxIODynamicDistributionType': 1
|
||||||
|
|
||||||
|
}
|
||||||
|
modifiedstorageGroupInstance = {
|
||||||
|
'CreationClassName': 'CIM_DeviceMaskingGroup',
|
||||||
|
'EMCMaximumIO': 6000,
|
||||||
|
'EMCMaximumBandwidth': 6000,
|
||||||
|
'EMCMaxIODynamicDistributionType': 1
|
||||||
|
|
||||||
|
}
|
||||||
|
conn.ModifyInstance = (
|
||||||
|
mock.Mock(return_value=modifiedstorageGroupInstance))
|
||||||
|
self.driver.common.utils.update_storagegroup_qos(
|
||||||
|
conn, storageGroupInstanceName, extraSpecs)
|
||||||
|
|
||||||
|
modifiedInstance = self.driver.common.utils.update_storagegroup_qos(
|
||||||
|
conn, storageGroupInstanceName, extraSpecs)
|
||||||
|
self.assertIsNotNone(modifiedInstance)
|
||||||
|
self.assertEqual(
|
||||||
|
6000, modifiedInstance['EMCMaximumIO'])
|
||||||
|
self.assertEqual(
|
||||||
|
6000, modifiedInstance['EMCMaximumBandwidth'])
|
||||||
|
self.assertEqual(
|
||||||
|
1, modifiedInstance['EMCMaxIODynamicDistributionType'])
|
||||||
|
self.assertEqual('CIM_DeviceMaskingGroup',
|
||||||
|
modifiedInstance['CreationClassName'])
|
||||||
|
|
||||||
|
|
||||||
class EMCVMAXCommonTest(test.TestCase):
|
class EMCVMAXCommonTest(test.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
@ -1306,6 +1306,7 @@ class EMCVMAXCommon(object):
|
|||||||
:returns: string -- configuration file
|
:returns: string -- configuration file
|
||||||
"""
|
"""
|
||||||
extraSpecs = self.utils.get_volumetype_extraspecs(volume, volumeTypeId)
|
extraSpecs = self.utils.get_volumetype_extraspecs(volume, volumeTypeId)
|
||||||
|
qosSpecs = self.utils.get_volumetype_qosspecs(volume, volumeTypeId)
|
||||||
configGroup = None
|
configGroup = None
|
||||||
|
|
||||||
# If there are no extra specs then the default case is assumed.
|
# If there are no extra specs then the default case is assumed.
|
||||||
@ -1313,8 +1314,7 @@ class EMCVMAXCommon(object):
|
|||||||
configGroup = self.configuration.config_group
|
configGroup = self.configuration.config_group
|
||||||
configurationFile = self._register_config_file_from_config_group(
|
configurationFile = self._register_config_file_from_config_group(
|
||||||
configGroup)
|
configGroup)
|
||||||
|
return extraSpecs, configurationFile, qosSpecs
|
||||||
return extraSpecs, configurationFile
|
|
||||||
|
|
||||||
def _get_ecom_connection(self):
|
def _get_ecom_connection(self):
|
||||||
"""Get the ecom connection.
|
"""Get the ecom connection.
|
||||||
@ -1751,7 +1751,7 @@ class EMCVMAXCommon(object):
|
|||||||
:raises: VolumeBackendAPIException
|
:raises: VolumeBackendAPIException
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
extraSpecs, configurationFile = (
|
extraSpecs, configurationFile, qosSpecs = (
|
||||||
self._set_config_file_and_get_extra_specs(
|
self._set_config_file_and_get_extra_specs(
|
||||||
volume, volumeTypeId))
|
volume, volumeTypeId))
|
||||||
|
|
||||||
@ -1777,6 +1777,9 @@ class EMCVMAXCommon(object):
|
|||||||
else:
|
else:
|
||||||
# V2 extra specs
|
# V2 extra specs
|
||||||
extraSpecs = self._set_v2_extra_specs(extraSpecs, poolRecord)
|
extraSpecs = self._set_v2_extra_specs(extraSpecs, poolRecord)
|
||||||
|
if (qosSpecs.get('qos_spec')
|
||||||
|
and qosSpecs['qos_specs']['consumer'] != "front-end"):
|
||||||
|
extraSpecs['qos'] = qosSpecs['qos_specs']['specs']
|
||||||
except Exception:
|
except Exception:
|
||||||
import sys
|
import sys
|
||||||
exceptionMessage = (_(
|
exceptionMessage = (_(
|
||||||
@ -2893,6 +2896,10 @@ class EMCVMAXCommon(object):
|
|||||||
LOG.error(exceptionMessage)
|
LOG.error(exceptionMessage)
|
||||||
raise exception.VolumeBackendAPIException(
|
raise exception.VolumeBackendAPIException(
|
||||||
data=exceptionMessage)
|
data=exceptionMessage)
|
||||||
|
# If qos exists, update storage group to reflect qos parameters
|
||||||
|
if 'qos' in extraSpecs:
|
||||||
|
self.utils.update_storagegroup_qos(
|
||||||
|
self.conn, defaultStorageGroupInstanceName, extraSpecs)
|
||||||
|
|
||||||
self._add_volume_to_default_storage_group_on_create(
|
self._add_volume_to_default_storage_group_on_create(
|
||||||
volumeDict, volumeName, storageConfigService,
|
volumeDict, volumeName, storageConfigService,
|
||||||
@ -2981,6 +2988,10 @@ class EMCVMAXCommon(object):
|
|||||||
sgInstanceName = self.provisionv3.create_storage_group_v3(
|
sgInstanceName = self.provisionv3.create_storage_group_v3(
|
||||||
self.conn, controllerConfigService, storageGroupName,
|
self.conn, controllerConfigService, storageGroupName,
|
||||||
poolName, slo, workload, extraSpecs)
|
poolName, slo, workload, extraSpecs)
|
||||||
|
# If qos exists, update storage group to reflect qos parameters
|
||||||
|
if 'qos' in extraSpecs:
|
||||||
|
self.utils.update_storagegroup_qos(
|
||||||
|
self.conn, sgInstanceName, extraSpecs)
|
||||||
|
|
||||||
return sgInstanceName
|
return sgInstanceName
|
||||||
|
|
||||||
|
@ -70,6 +70,7 @@ class EMCVMAXFCDriver(driver.FibreChannelDriver):
|
|||||||
2.4.0 - EMC VMAX - locking SG for concurrent threads (bug #1554634)
|
2.4.0 - EMC VMAX - locking SG for concurrent threads (bug #1554634)
|
||||||
- SnapVX licensing checks for VMAX3 (bug #1587017)
|
- SnapVX licensing checks for VMAX3 (bug #1587017)
|
||||||
- VMAX oversubscription Support (blueprint vmax-oversubscription)
|
- VMAX oversubscription Support (blueprint vmax-oversubscription)
|
||||||
|
- QoS support (blueprint vmax-qos)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
VERSION = "2.4.0"
|
VERSION = "2.4.0"
|
||||||
|
@ -76,6 +76,7 @@ class EMCVMAXISCSIDriver(driver.ISCSIDriver):
|
|||||||
2.4.0 - EMC VMAX - locking SG for concurrent threads (bug #1554634)
|
2.4.0 - EMC VMAX - locking SG for concurrent threads (bug #1554634)
|
||||||
- SnapVX licensing checks for VMAX3 (bug #1587017)
|
- SnapVX licensing checks for VMAX3 (bug #1587017)
|
||||||
- VMAX oversubscription Support (blueprint vmax-oversubscription)
|
- VMAX oversubscription Support (blueprint vmax-oversubscription)
|
||||||
|
- QoS support (blueprint vmax-qos)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -1083,6 +1083,28 @@ class EMCVMAXUtils(object):
|
|||||||
|
|
||||||
return extraSpecs
|
return extraSpecs
|
||||||
|
|
||||||
|
def get_volumetype_qosspecs(self, volume, volumeTypeId=None):
|
||||||
|
"""Get the qos specs.
|
||||||
|
|
||||||
|
:param volume: the volume dictionary
|
||||||
|
:param volumeTypeId: Optional override for volume['volume_type_id']
|
||||||
|
:returns: dict -- qosSpecs - the qos specs
|
||||||
|
"""
|
||||||
|
qosSpecs = {}
|
||||||
|
|
||||||
|
try:
|
||||||
|
if volumeTypeId:
|
||||||
|
type_id = volumeTypeId
|
||||||
|
else:
|
||||||
|
type_id = volume['volume_type_id']
|
||||||
|
if type_id is not None:
|
||||||
|
qosSpecs = volume_types.get_volume_type_qos_specs(type_id)
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
LOG.debug("Unable to get QoS specifications.")
|
||||||
|
|
||||||
|
return qosSpecs
|
||||||
|
|
||||||
def get_volume_type_name(self, volume):
|
def get_volume_type_name(self, volume):
|
||||||
"""Get the volume type name.
|
"""Get the volume type name.
|
||||||
|
|
||||||
@ -2645,3 +2667,51 @@ class EMCVMAXUtils(object):
|
|||||||
max_over_sub_ratio = float(max_sub_ratio_from_per)
|
max_over_sub_ratio = float(max_sub_ratio_from_per)
|
||||||
|
|
||||||
return max_over_sub_ratio
|
return max_over_sub_ratio
|
||||||
|
|
||||||
|
def update_storagegroup_qos(self, conn, storagegroup, extraspecs):
|
||||||
|
"""Update the storagegroupinstance with qos details.
|
||||||
|
|
||||||
|
If MaxIOPS or maxMBPS is in extraspecs, then DistributionType can be
|
||||||
|
modified in addition to MaxIOPS or/and maxMBPS
|
||||||
|
If MaxIOPS or maxMBPS is NOT in extraspecs, we check to see if
|
||||||
|
either is set in StorageGroup. If so, then DistributionType can be
|
||||||
|
modified
|
||||||
|
|
||||||
|
:param conn: connection to the ecom server
|
||||||
|
:param storagegroup: the storagegroup instance name
|
||||||
|
:param extraSpecs: extra specifications
|
||||||
|
"""
|
||||||
|
if type(storagegroup) is pywbem.cim_obj.CIMInstance:
|
||||||
|
storagegroupInstance = storagegroup
|
||||||
|
else:
|
||||||
|
storagegroupInstance = conn.GetInstance(storagegroup)
|
||||||
|
propertylist = []
|
||||||
|
if 'maxIOPS' in extraspecs.get('qos'):
|
||||||
|
maxiops = self.get_num(extraspecs.get('qos').get('maxIOPS'), '32')
|
||||||
|
if maxiops != storagegroupInstance['EMCMaximumIO']:
|
||||||
|
storagegroupInstance['EMCMaximumIO'] = maxiops
|
||||||
|
propertylist.append('EMCMaximumIO')
|
||||||
|
if 'maxMBPS' in extraspecs.get('qos'):
|
||||||
|
maxmbps = self.get_num(extraspecs.get('qos').get('maxMBPS'), '32')
|
||||||
|
if maxmbps != storagegroupInstance['EMCMaximumBandwidth']:
|
||||||
|
storagegroupInstance['EMCMaximumBandwidth'] = maxmbps
|
||||||
|
propertylist.append('EMCMaximumBandwidth')
|
||||||
|
if 'DistributionType' in extraspecs.get('qos') and (
|
||||||
|
propertylist or (
|
||||||
|
storagegroupInstance['EMCMaximumBandwidth'] != 0) or (
|
||||||
|
storagegroupInstance['EMCMaximumIO'] != 0)):
|
||||||
|
dynamicdict = {'never': 1, 'onfailure': 2, 'always': 3}
|
||||||
|
dynamicvalue = dynamicdict.get(
|
||||||
|
extraspecs.get('qos').get('DistributionType').lower())
|
||||||
|
if dynamicvalue:
|
||||||
|
distributiontype = self.get_num(dynamicvalue, '16')
|
||||||
|
if distributiontype != (
|
||||||
|
storagegroupInstance['EMCMaxIODynamicDistributionType']
|
||||||
|
):
|
||||||
|
storagegroupInstance['EMCMaxIODynamicDistributionType'] = (
|
||||||
|
distributiontype)
|
||||||
|
propertylist.append('EMCMaxIODynamicDistributionType')
|
||||||
|
if propertylist:
|
||||||
|
modifiedInstance = conn.ModifyInstance(storagegroupInstance,
|
||||||
|
PropertyList=propertylist)
|
||||||
|
return modifiedInstance
|
||||||
|
3
releasenotes/notes/vmax-qos-eb40ed35bd2f457d.yaml
Normal file
3
releasenotes/notes/vmax-qos-eb40ed35bd2f457d.yaml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- QoS support for the VMAX.
|
Loading…
Reference in New Issue
Block a user