EMC VMAX - Oversubscription support

When max_over_subscription_ratio is 2.0 the pool are oversubscribed
by a factor of 2, or 200% over subscribed.  The reserved_percentage
is the high water mark where by the physical remaining space cannot
be exceeded.  For example, if there is only 4% of physical space left
and the reserve percentage is 5, user would not be permitted to
provision a volume.  This is a safety mechanism to prevent a scenario
that a provisioning request failing due to insufficient raw space.

Change-Id: I55614d05701d461d4b0b85f6f57c49d98b014641
Implements: blueprint vmax-oversubscription
This commit is contained in:
Helen Walsh 2016-04-18 22:56:23 +01:00
parent 7a8804aa20
commit 5377ed5810
8 changed files with 217 additions and 42 deletions

View File

@ -301,8 +301,11 @@ class EMCVMAXCommonData(object):
poolname = 'gold'
totalmanagedspace_bits = '1000000000000'
subscribedcapacity_bits = '500000000000'
remainingmanagedspace_bits = '500000000000'
maxsubscriptionpercent = 150
totalmanagedspace_gbs = 931
subscribedcapacity_gbs = 466
subscribedcapacity_gbs = 465
remainingmanagedspace_gbs = 465
fake_host = 'HostX@Backend#gold+1234567891011'
fake_host_v3 = 'HostX@Backend#Bronze+SRP_1+1234567891011'
fake_host_2_v3 = 'HostY@Backend#SRP_1+1234567891011'
@ -1114,6 +1117,8 @@ class FakeEcomConnection(object):
pool['SystemName'] = self.data.storage_system
pool['TotalManagedSpace'] = self.data.totalmanagedspace_bits
pool['EMCSubscribedCapacity'] = self.data.subscribedcapacity_bits
pool['RemainingManagedSpace'] = self.data.remainingmanagedspace_bits
pool['EMCMaxSubscriptionPercent'] = self.data.maxsubscriptionpercent
return pool
def _getinstance_replicationgroup(self, objectpath):
@ -2843,22 +2848,25 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase):
# Bug 1393555 - policy has been deleted by another process.
def test_get_capacities_associated_to_policy(self):
conn = self.fake_ecom_connection()
total_capacity_gb, free_capacity_gb = (
(total_capacity_gb, free_capacity_gb, provisioned_capacity_gb,
array_max_over_subscription) = (
self.driver.common.fast.get_capacities_associated_to_policy(
conn, self.data.storage_system, self.data.policyrule))
# The capacities associated to the policy have been found.
self.assertEqual(self.data.totalmanagedspace_gbs, total_capacity_gb)
self.assertEqual(self.data.subscribedcapacity_gbs, free_capacity_gb)
self.assertEqual(self.data.remainingmanagedspace_gbs, free_capacity_gb)
self.driver.common.fast.utils.get_existing_instance = mock.Mock(
return_value=None)
total_capacity_gb_2, free_capacity_gb_2 = (
(total_capacity_gb_2, free_capacity_gb_2, provisioned_capacity_gb_2,
array_max_over_subscription_2) = (
self.driver.common.fast.get_capacities_associated_to_policy(
conn, self.data.storage_system, self.data.policyrule))
# The capacities have not been found as the policy has been
# removed externally.
self.assertEqual(0, total_capacity_gb_2)
self.assertEqual(0, free_capacity_gb_2)
self.assertEqual(0, provisioned_capacity_gb_2)
# Bug 1393555 - storage group has been deleted by another process.
def test_find_storage_masking_group(self):
@ -2994,7 +3002,7 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase):
@mock.patch.object(
emc_vmax_utils.EMCVMAXUtils,
'get_pool_capacities',
return_value=(1234, 1200))
return_value=(1234, 1200, 1200, 1))
@mock.patch.object(
emc_vmax_fast.EMCVMAXFast,
'is_tiering_policy_enabled',
@ -3824,7 +3832,7 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase):
@mock.patch.object(
emc_vmax_common.EMCVMAXCommon,
'_update_pool_stats',
return_value={1, 2, 3})
return_value={1, 2, 3, 4, 5})
def test_ssl_support(self, pool_stats):
self.driver.common.update_volume_stats()
self.assertTrue(self.driver.common.ecomUseSSL)
@ -3955,7 +3963,11 @@ class EMCVMAXISCSIDriverFastTestCase(test.TestCase):
@mock.patch.object(
emc_vmax_fast.EMCVMAXFast,
'get_capacities_associated_to_policy',
return_value=(1234, 1200))
return_value=(1234, 1200, 1200, 1))
@mock.patch.object(
emc_vmax_utils.EMCVMAXUtils,
'get_pool_capacities',
return_value=(1234, 1200, 1200, 1))
@mock.patch.object(
emc_vmax_fast.EMCVMAXFast,
'get_tier_policy_by_name',
@ -3972,7 +3984,8 @@ class EMCVMAXISCSIDriverFastTestCase(test.TestCase):
mock_storage_system,
mock_is_fast_enabled,
mock_get_policy,
mock_capacity):
mock_pool_capacities,
mock_capacities_associated_to_policy):
self.driver.get_volume_stats(True)
@mock.patch.object(
@ -4591,7 +4604,7 @@ class EMCVMAXFCDriverNoFastTestCase(test.TestCase):
@mock.patch.object(
emc_vmax_utils.EMCVMAXUtils,
'get_pool_capacities',
return_value=(1234, 1200))
return_value=(1234, 1200, 1200, 1))
@mock.patch.object(
emc_vmax_fast.EMCVMAXFast,
'is_tiering_policy_enabled',
@ -5163,7 +5176,11 @@ class EMCVMAXFCDriverFastTestCase(test.TestCase):
@mock.patch.object(
emc_vmax_fast.EMCVMAXFast,
'get_capacities_associated_to_policy',
return_value=(1234, 1200))
return_value=(1234, 1200, 1200, 1))
@mock.patch.object(
emc_vmax_utils.EMCVMAXUtils,
'get_pool_capacities',
return_value=(1234, 1200, 1200, 1))
@mock.patch.object(
emc_vmax_fast.EMCVMAXFast,
'get_tier_policy_by_name',
@ -5180,7 +5197,8 @@ class EMCVMAXFCDriverFastTestCase(test.TestCase):
mock_storage_system,
mock_is_fast_enabled,
mock_get_policy,
mock_capacity):
mock_pool_capacities,
mock_capacities_associated_to_policy):
self.driver.get_volume_stats(True)
@mock.patch.object(
@ -5734,7 +5752,7 @@ class EMCV3DriverTestCase(test.TestCase):
def set_configuration(self):
configuration = mock.Mock()
configuration.cinder_emc_config_file = self.config_file_path
configuration.safe_get.return_value = 'V3'
configuration.safe_get.return_value = 3
configuration.config_group = 'V3'
self.stubs.Set(emc_vmax_common.EMCVMAXCommon, '_get_ecom_connection',
@ -7944,6 +7962,45 @@ class EMCVMAXUtilsTest(test.TestCase):
self.assertFalse(self.driver.utils.is_clone_licensed(
conn, capabilityInstanceName, isV3))
def test_get_pool_capacities(self):
conn = FakeEcomConnection()
(total_capacity_gb, free_capacity_gb, provisioned_capacity_gb,
array_max_over_subscription) = (
self.driver.utils.get_pool_capacities(
conn, self.data.poolname, self.data.storage_system))
self.assertEqual(931, total_capacity_gb)
self.assertEqual(465, free_capacity_gb)
self.assertEqual(465, provisioned_capacity_gb)
self.assertEqual(1.5, array_max_over_subscription)
def test_get_pool_capacities_none_array_max_oversubscription(self):
conn = FakeEcomConnection()
null_emcmaxsubscriptionpercent = {
'TotalManagedSpace': '1000000000000',
'ElementName': 'gold',
'RemainingManagedSpace': '500000000000',
'SystemName': 'SYMMETRIX+000195900551',
'CreationClassName': 'Symm_VirtualProvisioningPool',
'EMCSubscribedCapacity': '500000000000'}
conn.GetInstance = mock.Mock(
return_value=null_emcmaxsubscriptionpercent)
(total_capacity_gb, free_capacity_gb, provisioned_capacity_gb,
array_max_over_subscription) = (
self.driver.utils.get_pool_capacities(
conn, self.data.poolname, self.data.storage_system))
self.assertEqual(65534, array_max_over_subscription)
def test_get_ratio_from_max_sub_per(self):
max_subscription_percent_float = (
self.driver.utils.get_ratio_from_max_sub_per(150))
self.assertEqual(1.5, max_subscription_percent_float)
def test_get_ratio_from_max_sub_per_none_value(self):
max_subscription_percent_float = (
self.driver.utils.get_ratio_from_max_sub_per(str(0)))
self.assertIsNone(max_subscription_percent_float)
class EMCVMAXCommonTest(test.TestCase):
def setUp(self):

View File

@ -102,7 +102,10 @@ class EMCVMAXCommon(object):
pool_info = {'backend_name': None,
'config_file': None,
'arrays_info': {}}
'arrays_info': {},
'max_over_subscription_ratio': None,
'reserved_percentage': None
}
def __init__(self, prtcl, version, configuration=None):
@ -137,6 +140,10 @@ class EMCVMAXCommon(object):
self.pool_info['backend_name'] = (
self.configuration.safe_get('volume_backend_name'))
self.pool_info['max_over_subscription_ratio'] = (
self.configuration.safe_get('max_over_subscription_ratio'))
self.pool_info['reserved_percentage'] = (
self.configuration.safe_get('reserved_percentage'))
LOG.debug(
"Updating volume stats on file %(emcConfigFileName)s on "
"backend %(backendName)s.",
@ -626,20 +633,27 @@ class EMCVMAXCommon(object):
"""Retrieve stats info."""
pools = []
backendName = self.pool_info['backend_name']
max_oversubscription_ratio = (
self.pool_info['max_over_subscription_ratio'])
reservedPercentage = self.pool_info['reserved_percentage']
array_max_over_subscription = None
array_reserve_percent = None
for arrayInfo in self.pool_info['arrays_info']:
self._set_ecom_credentials(arrayInfo)
# Check what type of array it is
isV3 = self.utils.isArrayV3(self.conn, arrayInfo['SerialNumber'])
if isV3:
location_info, total_capacity_gb, free_capacity_gb = (
self._update_srp_stats(arrayInfo))
(location_info, total_capacity_gb, free_capacity_gb,
provisioned_capacity_gb,
array_reserve_percent) = self._update_srp_stats(arrayInfo)
poolName = ("%(slo)s+%(poolName)s+%(array)s"
% {'slo': arrayInfo['SLO'],
'poolName': arrayInfo['PoolName'],
'array': arrayInfo['SerialNumber']})
else:
# This is V2
location_info, total_capacity_gb, free_capacity_gb = (
(location_info, total_capacity_gb, free_capacity_gb,
provisioned_capacity_gb, array_max_over_subscription) = (
self._update_pool_stats(backendName, arrayInfo))
poolName = ("%(poolName)s+%(array)s"
% {'poolName': arrayInfo['PoolName'],
@ -648,10 +662,25 @@ class EMCVMAXCommon(object):
pool = {'pool_name': poolName,
'total_capacity_gb': total_capacity_gb,
'free_capacity_gb': free_capacity_gb,
'reserved_percentage': 0,
'provisioned_capacity_gb': provisioned_capacity_gb,
'QoS_support': False,
'location_info': location_info,
'consistencygroup_support': True}
'consistencygroup_support': True,
'thin_provisioning_support': True,
'thick_provisioning_support': False,
'max_over_subscription_ratio': max_oversubscription_ratio
}
if array_max_over_subscription:
pool['max_over_subscription_ratio'] = (
self.utils.override_ratio(
max_oversubscription_ratio,
array_max_over_subscription))
if array_reserve_percent and (
array_reserve_percent > reservedPercentage):
pool['reserved_percentage'] = array_reserve_percent
else:
pool['reserved_percentage'] = reservedPercentage
pools.append(pool)
data = {'vendor_name': "EMC",
@ -662,6 +691,7 @@ class EMCVMAXCommon(object):
# Use zero capacities here so we always use a pool.
'total_capacity_gb': 0,
'free_capacity_gb': 0,
'provisioned_capacity_gb': 0,
'reserved_percentage': 0,
'pools': pools}
@ -674,20 +704,24 @@ class EMCVMAXCommon(object):
:returns: location_info
:returns: totalManagedSpaceGbs
:returns: remainingManagedSpaceGbs
:returns: provisionedManagedSpaceGbs
:returns: array_reserve_percent
"""
totalManagedSpaceGbs, remainingManagedSpaceGbs = (
self.provisionv3.get_srp_pool_stats(self.conn,
arrayInfo))
(totalManagedSpaceGbs, remainingManagedSpaceGbs,
provisionedManagedSpaceGbs, array_reserve_percent) = (
self.provisionv3.get_srp_pool_stats(self.conn, arrayInfo))
LOG.info(_LI(
"Capacity stats for SRP pool %(poolName)s on array "
"%(arrayName)s total_capacity_gb=%(total_capacity_gb)lu, "
"free_capacity_gb=%(free_capacity_gb)lu"),
"free_capacity_gb=%(free_capacity_gb)lu, "
"provisioned_capacity_gb=%(provisioned_capacity_gb)lu"),
{'poolName': arrayInfo['PoolName'],
'arrayName': arrayInfo['SerialNumber'],
'total_capacity_gb': totalManagedSpaceGbs,
'free_capacity_gb': remainingManagedSpaceGbs})
'free_capacity_gb': remainingManagedSpaceGbs,
'provisioned_capacity_gb': provisionedManagedSpaceGbs})
location_info = ("%(arrayName)s#%(poolName)s#%(slo)s#%(workload)s"
% {'arrayName': arrayInfo['SerialNumber'],
@ -695,7 +729,9 @@ class EMCVMAXCommon(object):
'slo': arrayInfo['SLO'],
'workload': arrayInfo['Workload']})
return location_info, totalManagedSpaceGbs, remainingManagedSpaceGbs
return (location_info, totalManagedSpaceGbs,
remainingManagedSpaceGbs, provisionedManagedSpaceGbs,
array_reserve_percent)
def retype(self, ctxt, volume, new_type, diff, host):
"""Migrate volume to another host using retype.
@ -3193,7 +3229,8 @@ class EMCVMAXCommon(object):
:param backendName: the backend name
:param arrayInfo: the arrayInfo
:returns: location_info, total_capacity_gb, free_capacity_gb
:returns: location_info, total_capacity_gb, free_capacity_gb,
provisioned_capacity_gb
"""
if arrayInfo['FastPolicy']:
@ -3216,7 +3253,8 @@ class EMCVMAXCommon(object):
if (arrayInfo['FastPolicy'] is not None and
isTieringPolicySupported is True): # FAST enabled
total_capacity_gb, free_capacity_gb = (
(total_capacity_gb, free_capacity_gb, provisioned_capacity_gb,
array_max_over_subscription) = (
self.fast.get_capacities_associated_to_policy(
self.conn, arrayInfo['SerialNumber'],
arrayInfo['FastPolicy']))
@ -3229,7 +3267,8 @@ class EMCVMAXCommon(object):
'total_capacity_gb': total_capacity_gb,
'free_capacity_gb': free_capacity_gb})
else: # NON-FAST
total_capacity_gb, free_capacity_gb = (
(total_capacity_gb, free_capacity_gb, provisioned_capacity_gb,
array_max_over_subscription) = (
self.utils.get_pool_capacities(self.conn,
arrayInfo['PoolName'],
arrayInfo['SerialNumber']))
@ -3247,7 +3286,8 @@ class EMCVMAXCommon(object):
'poolName': arrayInfo['PoolName'],
'policyName': arrayInfo['FastPolicy']})
return location_info, total_capacity_gb, free_capacity_gb
return (location_info, total_capacity_gb, free_capacity_gb,
provisioned_capacity_gb, array_max_over_subscription)
def _set_v2_extra_specs(self, extraSpecs, poolRecord):
"""Set the VMAX V2 extra specs.

View File

@ -697,14 +697,19 @@ class EMCVMAXFast(object):
:param policyName: the name of policy rule, a string value
:returns: int -- total capacity in GB of all pools associated with
the policy
:returns: int -- (total capacity-EMCSubscribedCapacity) in GB of all
pools associated with the policy
:returns: int -- real physical capacity in GB of all pools
available to be used
:returns: int -- (Provisioned capacity-EMCSubscribedCapacity) in GB
is the the capacity that has been provisioned
:returns: int -- the maximum oversubscription ration
"""
policyInstanceName = self.get_tier_policy_by_name(
conn, arrayName, policyName)
total_capacity_gb = 0
allocated_capacity_gb = 0
provisioned_capacity_gb = 0
free_capacity_gb = 0
array_max_over_subscription = None
tierInstanceNames = self.get_associated_tier_from_tier_policy(
conn, policyInstanceName)
@ -726,17 +731,25 @@ class EMCVMAXFast(object):
break
total_capacity_gb += self.utils.convert_bits_to_gbs(
storagePoolInstance['TotalManagedSpace'])
allocated_capacity_gb += self.utils.convert_bits_to_gbs(
provisioned_capacity_gb += self.utils.convert_bits_to_gbs(
storagePoolInstance['EMCSubscribedCapacity'])
free_capacity_gb += self.utils.convert_bits_to_gbs(
storagePoolInstance['RemainingManagedSpace'])
try:
array_max_over_subscription = (
self.utils.get_ratio_from_max_sub_per(
storagePoolInstance['EMCMaxSubscriptionPercent']))
except KeyError:
array_max_over_subscription = 65534
LOG.debug(
"PolicyName:%(policyName)s, pool: %(poolInstanceName)s, "
"allocated_capacity_gb = %(allocated_capacity_gb)lu.",
"provisioned_capacity_gb = %(provisioned_capacity_gb)lu.",
{'policyName': policyName,
'poolInstanceName': poolInstanceName,
'allocated_capacity_gb': allocated_capacity_gb})
'provisioned_capacity_gb': provisioned_capacity_gb})
free_capacity_gb = total_capacity_gb - allocated_capacity_gb
return (total_capacity_gb, free_capacity_gb)
return (total_capacity_gb, free_capacity_gb,
provisioned_capacity_gb, array_max_over_subscription)
def get_or_create_default_storage_group(
self, conn, controllerConfigService, fastPolicyName,

View File

@ -69,7 +69,7 @@ class EMCVMAXFCDriver(driver.FibreChannelDriver):
- Operations and timeout issues (bug #1538214)
2.4.0 - EMC VMAX - locking SG for concurrent threads (bug #1554634)
- SnapVX licensing checks for VMAX3 (bug #1587017)
- VMAX oversubscription Support (blueprint vmax-oversubscription)
"""
VERSION = "2.4.0"

View File

@ -75,6 +75,7 @@ class EMCVMAXISCSIDriver(driver.ISCSIDriver):
- Operations and timeout issues (bug #1538214)
2.4.0 - EMC VMAX - locking SG for concurrent threads (bug #1554634)
- SnapVX licensing checks for VMAX3 (bug #1587017)
- VMAX oversubscription Support (blueprint vmax-oversubscription)
"""

View File

@ -704,9 +704,13 @@ class EMCVMAXProvisionV3(object):
:param arrayInfo: the array dict
:returns: totalCapacityGb
:returns: remainingCapacityGb
:returns: subscribedCapacityGb
:returns: array_reserve_percent
"""
totalCapacityGb = -1
remainingCapacityGb = -1
subscribedCapacityGb = -1
array_reserve_percent = -1
storageSystemInstanceName = self.utils.find_storageSystem(
conn, arrayInfo['SerialNumber'])
@ -735,6 +739,15 @@ class EMCVMAXProvisionV3(object):
remainingCapacityGb = (
self.utils.convert_bits_to_gbs(
remainingManagedSpace))
elif properties[0] == 'EMCSubscribedCapacity':
cimProperties = properties[1]
subscribedManagedSpace = cimProperties.value
subscribedCapacityGb = (
self.utils.convert_bits_to_gbs(
subscribedManagedSpace))
elif properties[0] == 'EMCPercentReservedCapacity':
cimProperties = properties[1]
array_reserve_percent = int(cimProperties.value)
except Exception:
pass
remainingSLOCapacityGb = (
@ -751,7 +764,8 @@ class EMCVMAXProvisionV3(object):
"not be what you expect."),
{'remainingCapacityGb': remainingCapacityGb})
return totalCapacityGb, remainingCapacityGb
return (totalCapacityGb, remainingCapacityGb, subscribedCapacityGb,
array_reserve_percent)
def _get_remaining_slo_capacity_wlp(self, conn, srpPoolInstanceName,
arrayInfo, systemName):

View File

@ -978,7 +978,8 @@ class EMCVMAXUtils(object):
:param conn: connection to the ecom server
:param poolName: string value of the storage pool name
:param storageSystemName: the storage system name
:returns: tuple -- (total_capacity_gb, free_capacity_gb)
:returns: tuple -- (total_capacity_gb, free_capacity_gb,
provisioned_capacity_gb)
"""
LOG.debug(
"Retrieving capacity for pool %(poolName)s on array %(array)s.",
@ -997,10 +998,17 @@ class EMCVMAXUtils(object):
poolInstanceName, LocalOnly=False)
total_capacity_gb = self.convert_bits_to_gbs(
storagePoolInstance['TotalManagedSpace'])
allocated_capacity_gb = self.convert_bits_to_gbs(
provisioned_capacity_gb = self.convert_bits_to_gbs(
storagePoolInstance['EMCSubscribedCapacity'])
free_capacity_gb = total_capacity_gb - allocated_capacity_gb
return (total_capacity_gb, free_capacity_gb)
free_capacity_gb = self.convert_bits_to_gbs(
storagePoolInstance['RemainingManagedSpace'])
try:
array_max_over_subscription = self.get_ratio_from_max_sub_per(
storagePoolInstance['EMCMaxSubscriptionPercent'])
except KeyError:
array_max_over_subscription = 65534
return (total_capacity_gb, free_capacity_gb,
provisioned_capacity_gb, array_max_over_subscription)
def get_pool_by_name(self, conn, storagePoolName, storageSystemName):
"""Returns the instance name associated with a storage pool name.
@ -2598,3 +2606,42 @@ class EMCVMAXUtils(object):
sgInstanceName = self.find_storage_masking_group(
conn, controllerConfigService, storageGroupName)
return storageGroupName, controllerConfigService, sgInstanceName
def get_ratio_from_max_sub_per(self, max_subscription_percent):
"""Get ratio from max subscription percent if it exists.
Check if the max subscription is set on the pool, if it is convert
it to a ratio.
:param max_subscription_percent: max subscription percent
:returns: max_over_subscription_ratio
"""
if max_subscription_percent == '0':
return None
try:
max_subscription_percent_int = int(max_subscription_percent)
except ValueError:
LOG.error(_LE("Cannot convert max subscription percent to int."))
return None
return float(max_subscription_percent_int) / 100
def override_ratio(self, max_over_sub_ratio, max_sub_ratio_from_per):
"""Override ratio if necessary
The over subscription ratio will be overriden if the max subscription
percent is less than the user supplied max oversubscription ratio.
:param max_over_sub_ratio: user supplied over subscription ratio
:param max_sub_ratio_from_per: property on the pool
:returns: max_over_sub_ratio
"""
if max_over_sub_ratio:
try:
max_over_sub_ratio = max(float(max_over_sub_ratio),
float(max_sub_ratio_from_per))
except ValueError:
max_over_sub_ratio = float(max_sub_ratio_from_per)
elif max_sub_ratio_from_per:
max_over_sub_ratio = float(max_sub_ratio_from_per)
return max_over_sub_ratio

View File

@ -0,0 +1,3 @@
---
features:
- Added oversubscription support in the VMAX driver