Merge "VMAX Driver - Fix for get-pools and returned Service Levels"

This commit is contained in:
Zuul 2018-08-07 23:26:58 +00:00 committed by Gerrit Code Review
commit e2271f7613
7 changed files with 170 additions and 55 deletions

View File

@ -538,6 +538,18 @@ class VMAXCommonData(object):
"fba_used_capacity": 5244.7,
"reserved_cap_percent": 10}
array_info_wl = {'RestServerIp': '1.1.1.1', 'RestServerPort': 3448,
'RestUserName': 'smc', 'RestPassword': 'smc',
'SSLVerify': False, 'SerialNumber': array,
'srpName': 'SRP_1', 'PortGroup': port_group_name_i,
'SLO': 'Diamond', 'Workload': 'OLTP'}
array_info_no_wl = {'RestServerIp': '1.1.1.1', 'RestServerPort': 3448,
'RestUserName': 'smc', 'RestPassword': 'smc',
'SSLVerify': False, 'SerialNumber': array,
'srpName': 'SRP_1', 'PortGroup': port_group_name_i,
'SLO': 'Diamond'}
volume_details = [{"cap_gb": 2,
"num_of_storage_groups": 1,
"volumeId": device_id,
@ -593,11 +605,19 @@ class VMAXCommonData(object):
"remoteDeviceID": device_id2,
"remoteSymmetrixID": remote_array}]}}]}}
# Service Levels / Workloads
workloadtype = {"workloadId": ["OLTP", "OLTP_REP", "DSS", "DSS_REP"]}
srp_slo_details = {"serviceLevelDemand": [
{"serviceLevelId": "None"}, {"serviceLevelId": "Diamond"},
{"serviceLevelId": "Gold"}, {"serviceLevelId": "Optimized"}]}
slo_details = ['None', 'Diamond', 'Gold', 'Optimized']
powermax_slo_details = {"sloId": ["Bronze", "Diamond", "Gold",
"Optimized", "Platinum", "Silver"]}
powermax_model_details = {"symmetrixId": array,
"model": "PowerMax_2000",
"ucode": "5978.1091.1092"}
vmax_slo_details = {"sloId": ["Diamond", "Optimized"]}
vmax_model_details = {"model": "VMAX450F"}
# replication
volume_snap_vx = {"snapshotLnks": [],
@ -1025,6 +1045,8 @@ class FakeRequestsSession(object):
return_object = self.data.workloadtype
elif 'compressionCapable' in url:
return_object = self.data.compression_info
elif 'slo' in url:
return_object = self.data.powermax_slo_details
elif 'replication' in url:
return_object = self._replication(url)
@ -1853,11 +1875,18 @@ class VMAXRestTest(test.TestCase):
self.data.array, self.data.srp)
self.assertEqual(ref_details, srp_details)
def test_get_slo_list(self):
ref_settings = self.data.slo_details
slo_settings = self.rest.get_slo_list(self.data.array, self.data.srp)
def test_get_slo_list_powermax(self):
ref_settings = self.data.powermax_slo_details['sloId']
slo_settings = self.rest.get_slo_list(self.data.array)
self.assertEqual(ref_settings, slo_settings)
def test_get_slo_list_vmax(self):
ref_settings = ['Diamond']
with mock.patch.object(self.rest, 'get_resource',
return_value=self.data.vmax_slo_details):
slo_settings = self.rest.get_slo_list(self.data.array)
self.assertEqual(ref_settings, slo_settings)
def test_get_workload_settings(self):
ref_settings = self.data.workloadtype['workloadId']
wl_settings = self.rest.get_workload_settings(
@ -3193,6 +3222,13 @@ class VMAXRestTest(test.TestCase):
# second iterator page
self.assertTrue(2 == len(result_list))
def test_get_vmax_model(self):
reference = 'PowerMax_2000'
with mock.patch.object(self.rest, '_get_request',
return_value=self.data.powermax_model_details):
self.assertEqual(self.rest.get_vmax_model(self.data.array),
reference)
class VMAXProvisionTest(test.TestCase):
def setUp(self):
@ -3739,7 +3775,17 @@ class VMAXCommonTest(test.TestCase):
configuration = FakeConfiguration(None, 'config_group', None, None)
fc.VMAXFCDriver(configuration=configuration)
def test_get_slo_workload_combinations_success(self):
def test_get_slo_workload_combinations_powermax(self):
array_info = self.common.get_attributes_from_cinder_config()
finalarrayinfolist = self.common._get_slo_workload_combinations(
array_info)
self.assertTrue(len(finalarrayinfolist) > 1)
@mock.patch.object(rest.VMAXRest, 'get_vmax_model',
return_value=VMAXCommonData.vmax_model_details['model'])
@mock.patch.object(rest.VMAXRest, 'get_slo_list',
return_value=VMAXCommonData.vmax_slo_details['sloId'])
def test_get_slo_workload_combinations_vmax(self, mck_slo, mck_model):
array_info = self.common.get_attributes_from_cinder_config()
finalarrayinfolist = self.common._get_slo_workload_combinations(
array_info)
@ -4074,6 +4120,20 @@ class VMAXCommonTest(test.TestCase):
data = self.common.update_volume_stats()
self.assertEqual('CommonTests', data['volume_backend_name'])
def test_update_srp_stats_with_wl(self):
with mock.patch.object(self.rest, 'get_srp_by_name',
return_value=self.data.srp_details):
location_info, __, __, __, __ = self.common._update_srp_stats(
self.data.array_info_wl)
self.assertEqual(location_info, '000197800123#SRP_1#Diamond#OLTP')
def test_update_srp_stats_no_wl(self):
with mock.patch.object(self.rest, 'get_srp_by_name',
return_value=self.data.srp_details):
location_info, __, __, __, __ = self.common._update_srp_stats(
self.data.array_info_no_wl)
self.assertEqual(location_info, '000197800123#SRP_1#Diamond')
def test_find_device_on_array_success(self):
volume = self.data.test_volume
extra_specs = self.data.extra_specs
@ -4407,8 +4467,10 @@ class VMAXCommonTest(test.TestCase):
def test_set_vmax_extra_specs_no_srp_name(self):
srp_record = self.common.get_attributes_from_cinder_config()
extra_specs = self.common._set_vmax_extra_specs({}, srp_record)
self.assertEqual('Optimized', extra_specs['slo'])
with mock.patch.object(self.rest, 'get_slo_list',
return_value=[]):
extra_specs = self.common._set_vmax_extra_specs({}, srp_record)
self.assertIsNone(extra_specs['slo'])
def test_set_vmax_extra_specs_compr_disabled(self):
with mock.patch.object(self.rest, 'is_compression_capable',

View File

@ -215,35 +215,44 @@ class VMAXCommon(object):
"""
try:
array = array_info['SerialNumber']
srp = array_info['srpName']
if self.failover:
array = self.active_backend_id
# Get the srp slo & workload settings
slo_settings = self.rest.get_slo_list(array, srp)
# Remove 'None' from the list (so a 'None' slo is not combined
# with a workload, which is not permitted)
slo_settings = self.rest.get_slo_list(array)
# Remove 'None' and 'Optimized from SL list, they cannot be mixed
# with workloads so will be added later again
slo_list = [x for x in slo_settings
if x.lower() not in ['none', 'optimized']]
workload_settings = self.rest.get_workload_settings(array)
workload_settings.append("None")
slo_workload_set = set(
['%(slo)s:%(workload)s' % {'slo': slo, 'workload': workload}
for slo in slo_list for workload in workload_settings])
# Add back in in the only allowed 'None' slo/ workload combination
slo_workload_set.add('None:None')
for x in slo_settings:
if 'optimized' == x.lower():
slo_workload_set.add('Optimized:None')
break
slo_workload_set = set()
if self.rest.is_next_gen_array(array):
for slo in slo_list:
slo_workload_set.add(slo)
slo_workload_set.add('None')
slo_workload_set.add('Optimized')
else:
slo_workload_set = set(
['%(slo)s:%(workload)s' % {'slo': slo,
'workload': workload}
for slo in slo_list for workload in workload_settings])
slo_workload_set.add('None:None')
if not any(self.rest.get_vmax_model(array) in x for x in
utils.VMAX_AFA_MODELS) and not \
self.rest.is_next_gen_array(array):
slo_workload_set.add('Optimized:None')
finalarrayinfolist = []
for sloWorkload in slo_workload_set:
# Doing a shallow copy will work as we are modifying
# only strings
temparray_info = array_info.copy()
slo, workload = sloWorkload.split(':')
temparray_info['SLO'] = slo
temparray_info['Workload'] = workload
try:
slo, workload = sloWorkload.split(':')
temparray_info['SLO'] = slo
temparray_info['Workload'] = workload
except ValueError:
temparray_info['SLO'] = sloWorkload
finalarrayinfolist.append(temparray_info)
except Exception as e:
exception_message = (_(
@ -870,21 +879,35 @@ class VMAXCommon(object):
provisioned_capacity_gb, array_reserve_percent])
else:
already_queried = True
pool_name = ("%(slo)s+%(workload)s+%(srpName)s+%(array)s"
% {'slo': array_info['SLO'],
'workload': array_info['Workload'],
'srpName': array_info['srpName'],
'array': array_info['SerialNumber']})
try:
pool_name = ("%(slo)s+%(workload)s+%(srpName)s+%(array)s"
% {'slo': array_info['SLO'],
'workload': array_info['Workload'],
'srpName': array_info['srpName'],
'array': array_info['SerialNumber']})
except KeyError:
pool_name = ("%(slo)s+%(srpName)s+%(array)s"
% {'slo': array_info['SLO'],
'srpName': array_info['srpName'],
'array': array_info['SerialNumber']})
if already_queried:
# The dictionary will only have one key per VMAX
# Construct the location info
temp_location_info = (
("%(arrayName)s#%(srpName)s#%(slo)s#%(workload)s"
% {'arrayName': array_info['SerialNumber'],
'srpName': array_info['srpName'],
'slo': array_info['SLO'],
'workload': array_info['Workload']}))
try:
temp_location_info = (
("%(arrayName)s#%(srpName)s#%(slo)s#%(workload)s"
% {'arrayName': array_info['SerialNumber'],
'srpName': array_info['srpName'],
'slo': array_info['SLO'],
'workload': array_info['Workload']}))
except KeyError:
temp_location_info = (
("%(arrayName)s#%(srpName)s#%(slo)s"
% {'arrayName': array_info['SerialNumber'],
'srpName': array_info['srpName'],
'slo': array_info['SLO']}))
pool = {'pool_name': pool_name,
'total_capacity_gb':
arrays[array_info['SerialNumber']][0],
@ -981,11 +1004,17 @@ class VMAXCommon(object):
'free_capacity_gb': remainingManagedSpaceGbs,
'provisioned_capacity_gb': provisionedManagedSpaceGbs})
location_info = ("%(arrayName)s#%(srpName)s#%(slo)s#%(workload)s"
% {'arrayName': array_info['SerialNumber'],
'srpName': array_info['srpName'],
'slo': array_info['SLO'],
'workload': array_info['Workload']})
try:
location_info = ("%(arrayName)s#%(srpName)s#%(slo)s#%(workload)s"
% {'arrayName': array_info['SerialNumber'],
'srpName': array_info['srpName'],
'slo': array_info['SLO'],
'workload': array_info['Workload']})
except KeyError:
location_info = ("%(arrayName)s#%(srpName)s#%(slo)s"
% {'arrayName': array_info['SerialNumber'],
'srpName': array_info['srpName'],
'slo': array_info['SLO']})
return (location_info, totalManagedSpaceGbs,
remainingManagedSpaceGbs, provisionedManagedSpaceGbs,
@ -1561,8 +1590,7 @@ class VMAXCommon(object):
'wl': workload_from_extra_spec})
else:
slo_list = self.rest.get_slo_list(
pool_record['SerialNumber'], extra_specs[utils.SRP])
slo_list = self.rest.get_slo_list(pool_record['SerialNumber'])
if 'Optimized' in slo_list:
slo_from_extra_spec = 'Optimized'
elif 'Diamond' in slo_list:
@ -1604,6 +1632,7 @@ class VMAXCommon(object):
self.version_dict = (
self.volume_metadata.gather_version_info(
extra_specs[utils.ARRAY]))
return extra_specs
def _delete_from_srp(self, array, device_id, volume_name,

View File

@ -97,6 +97,7 @@ class VMAXFCDriver(san.SanDriver, driver.FibreChannelDriver):
(bp/vmax-list-manage-existing)
- Fix for SSL verification/cert application (bug #1772924)
- Log VMAX metadata of a volume (bp vmax-metadata)
- Fix for get-pools command (bug #1784856)
"""
VERSION = "3.2.0"

View File

@ -102,6 +102,7 @@ class VMAXISCSIDriver(san.SanISCSIDriver):
(bp/vmax-list-manage-existing)
- Fix for SSL verification/cert application (bug #1772924)
- Log VMAX metadata of a volume (bp vmax-metadata)
- Fix for get-pools command (bug #1784856)
"""
VERSION = "3.2.0"

View File

@ -457,7 +457,7 @@ class VMAXProvision(object):
if slo and slo.lower() == 'none':
slo = None
valid_slos = self.rest.get_slo_list(array, srp)
valid_slos = self.rest.get_slo_list(array)
valid_workloads = self.rest.get_workload_settings(array)
for valid_slo in valid_slos:
if slo == valid_slo:

View File

@ -34,6 +34,7 @@ requests.packages.urllib3.disable_warnings(urllib_exp.InsecureRequestWarning)
LOG = logging.getLogger(__name__)
SLOPROVISIONING = 'sloprovisioning'
REPLICATION = 'replication'
SYSTEM = 'system'
U4V_VERSION = '84'
UCODE_5978 = '5978'
retry_exc_tuple = (exception.VolumeBackendAPIException,)
@ -461,20 +462,21 @@ class VMAXRest(object):
resource_name=srp, params=None)
return srp_details
def get_slo_list(self, array, srp):
def get_slo_list(self, array):
"""Retrieve the list of slo's from the array
:param array: the array serial number
:param srp: return service levels associated with this srp
:returns: slo_list -- list of service level names
"""
slo_list = []
res_name = '%s/service_level_demand_report' % srp
slo_dict = self.get_resource(array, SLOPROVISIONING, 'srp',
resource_name=res_name, version='90')
if slo_dict and slo_dict.get('serviceLevelDemand'):
for d in slo_dict['serviceLevelDemand']:
slo = d.get('serviceLevelId')
slo_dict = self.get_resource(array, SLOPROVISIONING, 'slo',
version='90')
if slo_dict and slo_dict.get('sloId'):
if any(self.get_vmax_model(array) in x for x in
utils.VMAX_AFA_MODELS):
if 'Optimized' in slo_dict.get('sloId'):
slo_dict['sloId'].remove('Optimized')
for slo in slo_dict['sloId']:
if slo and slo not in slo_list:
slo_list.append(slo)
return slo_list
@ -494,6 +496,20 @@ class VMAXRest(object):
workload_setting = wl_details['workloadId']
return workload_setting
def get_vmax_model(self, array):
"""Get the VMAX model.
:param array: the array serial number
:return: the VMAX model
"""
vmax_version = ''
system_uri = ("/%(version)s/system/symmetrix/%(array)s" % {
'version': U4V_VERSION, 'array': array})
system_info = self._get_request(system_uri, SYSTEM)
if system_info and system_info.get('model'):
vmax_version = system_info.get('model')
return vmax_version
def is_compression_capable(self, array):
"""Check if array is compression capable.

View File

@ -37,6 +37,7 @@ FC = 'fc'
INTERVAL = 'interval'
RETRIES = 'retries'
VOLUME_ELEMENT_NAME_PREFIX = 'OS-'
VMAX_AFA_MODELS = ['VMAX250F', 'VMAX450F', 'VMAX850F', 'VMAX950F']
MAX_SRP_LENGTH = 16
TRUNCATE_5 = 5
TRUNCATE_27 = 27
@ -582,9 +583,14 @@ class VMAXUtils(object):
if 'none' in pool['pool_name'].lower():
extra_pools.append(pool)
for pool in extra_pools:
slo = pool['pool_name'].split('+')[0]
srp = pool['pool_name'].split('+')[2]
array = pool['pool_name'].split('+')[3]
try:
slo = pool['pool_name'].split('+')[0]
srp = pool['pool_name'].split('+')[2]
array = pool['pool_name'].split('+')[3]
except IndexError:
slo = pool['pool_name'].split('+')[0]
srp = pool['pool_name'].split('+')[1]
array = pool['pool_name'].split('+')[2]
new_pool_name = ('%(slo)s+%(srp)s+%(array)s'
% {'slo': slo, 'srp': srp, 'array': array})
new_pool = deepcopy(pool)