Dell PowerMax Driver - Add support for Unisphere for PowerMax 10.0

Changes are indicated below:
  - endpoint uri added to support v10 U4P (version checked first)
  - 'fba_srp_capacity' instead of 'srp_capacity'
  - 'microcode' instead of 'ucode'

Implements: blueprint powermax-v4-support
Change-Id: Ie9561eeb30a54539cbdfa3e0152ac5383bb02ec6
This commit is contained in:
olegnest 2022-06-03 11:45:44 +03:00 committed by Jean-Pierre Roquesalane
parent acb8711dcf
commit 567f2a1b08
13 changed files with 177 additions and 161 deletions

View File

@ -79,7 +79,8 @@ class PowerMaxData(object):
rdf_group_no_2 = '71' rdf_group_no_2 = '71'
rdf_group_no_3 = '72' rdf_group_no_3 = '72'
rdf_group_no_4 = '73' rdf_group_no_4 = '73'
u4v_version = '92' u4p_version = '92'
u4p_100_endpoint = '100'
storagegroup_name_source = 'Grp_source_sg' storagegroup_name_source = 'Grp_source_sg'
storagegroup_name_target = 'Grp_target_sg' storagegroup_name_target = 'Grp_target_sg'
group_snapshot_name = 'Grp_snapshot' group_snapshot_name = 'Grp_snapshot'
@ -944,6 +945,9 @@ class PowerMaxData(object):
powermax_model_details = {'symmetrixId': array, powermax_model_details = {'symmetrixId': array,
'model': 'PowerMax_2000', 'model': 'PowerMax_2000',
'ucode': '5978.1091.1092'} 'ucode': '5978.1091.1092'}
powermax_model_100 = {'symmetrixId': array,
'model': 'PowerMax_2500',
'microcode': '6079.65.0'}
vmax_slo_details = {'sloId': ['Diamond', 'Optimized']} vmax_slo_details = {'sloId': ['Diamond', 'Optimized']}
vmax_model_details = {'model': 'VMAX450F'} vmax_model_details = {'model': 'VMAX450F'}
@ -1348,6 +1352,7 @@ class PowerMaxData(object):
platform = 'Linux-4.4.0-104-generic-x86_64-with-Ubuntu-16.04-xenial' platform = 'Linux-4.4.0-104-generic-x86_64-with-Ubuntu-16.04-xenial'
unisphere_version = u'V9.2.0.0' unisphere_version = u'V9.2.0.0'
unisphere_version_90 = "V9.0.0.1" unisphere_version_90 = "V9.0.0.1"
unisphere_version_100 = "V10.0.0.0"
openstack_release = '12.0.0.0b3.dev401' openstack_release = '12.0.0.0b3.dev401'
openstack_version = '12.0.0' openstack_version = '12.0.0'
python_version = '2.7.12' python_version = '2.7.12'

View File

@ -1802,6 +1802,7 @@ class PowerMaxCommonTest(test.TestCase):
array, portgroup_name, initiator_group_name) array, portgroup_name, initiator_group_name)
def test_get_iscsi_ip_iqn_port(self): def test_get_iscsi_ip_iqn_port(self):
self.common.rest.u4p_version = self.data.u4p_100_endpoint
phys_port = '%(dir)s:%(port)s' % {'dir': self.data.iscsi_dir, phys_port = '%(dir)s:%(port)s' % {'dir': self.data.iscsi_dir,
'port': self.data.iscsi_port} 'port': self.data.iscsi_port}
ref_ip_iqn = [{'iqn': self.data.initiator, ref_ip_iqn = [{'iqn': self.data.initiator,
@ -1817,6 +1818,7 @@ class PowerMaxCommonTest(test.TestCase):
self.assertEqual(ref_ip_iqn, ip_iqn_list) self.assertEqual(ref_ip_iqn, ip_iqn_list)
def test_find_ip_and_iqns(self): def test_find_ip_and_iqns(self):
self.common.rest.u4p_version = self.data.u4p_100_endpoint
ref_ip_iqn = [{'iqn': self.data.initiator, ref_ip_iqn = [{'iqn': self.data.initiator,
'ip': self.data.ip, 'ip': self.data.ip,
'physical_port': self.data.iscsi_dir_port}] 'physical_port': self.data.iscsi_dir_port}]

View File

@ -99,6 +99,7 @@ class PowerMaxISCSITest(test.TestCase):
'iqn': self.data.initiator, 'iqn': self.data.initiator,
'physical_port': phys_port}], 'physical_port': phys_port}],
'is_multipath': False} 'is_multipath': False}
self.common.rest.u4p_version = self.data.u4p_version
with mock.patch.object(self.driver, 'get_iscsi_dict') as mock_get: with mock.patch.object(self.driver, 'get_iscsi_dict') as mock_get:
with mock.patch.object( with mock.patch.object(
self.common, 'get_port_group_from_masking_view', self.common, 'get_port_group_from_masking_view',
@ -109,6 +110,7 @@ class PowerMaxISCSITest(test.TestCase):
ref_dict, self.data.test_volume) ref_dict, self.data.test_volume)
def test_get_iscsi_dict_success(self): def test_get_iscsi_dict_success(self):
self.common.rest.u4p_version = self.data.u4p_version
ip_and_iqn = self.common._find_ip_and_iqns( ip_and_iqn = self.common._find_ip_and_iqns(
self.data.array, self.data.port_group_name_i) self.data.array, self.data.port_group_name_i)
host_lun_id = self.data.iscsi_device_info['hostlunid'] host_lun_id = self.data.iscsi_device_info['hostlunid']
@ -131,6 +133,7 @@ class PowerMaxISCSITest(test.TestCase):
device_info, self.data.test_volume) device_info, self.data.test_volume)
def test_get_iscsi_dict_metro(self): def test_get_iscsi_dict_metro(self):
self.common.rest.u4p_version = self.data.u4p_version
ip_and_iqn = self.common._find_ip_and_iqns( ip_and_iqn = self.common._find_ip_and_iqns(
self.data.array, self.data.port_group_name_i) self.data.array, self.data.port_group_name_i)
host_lun_id = self.data.iscsi_device_info_metro['hostlunid'] host_lun_id = self.data.iscsi_device_info_metro['hostlunid']
@ -148,6 +151,7 @@ class PowerMaxISCSITest(test.TestCase):
def test_vmax_get_iscsi_properties_one_target_no_auth(self): def test_vmax_get_iscsi_properties_one_target_no_auth(self):
vol = deepcopy(self.data.test_volume) vol = deepcopy(self.data.test_volume)
self.common.rest.u4p_version = self.data.u4p_version
ip_and_iqn = self.common._find_ip_and_iqns( ip_and_iqn = self.common._find_ip_and_iqns(
self.data.array, self.data.port_group_name_i) self.data.array, self.data.port_group_name_i)
host_lun_id = self.data.iscsi_device_info['hostlunid'] host_lun_id = self.data.iscsi_device_info['hostlunid']

View File

@ -862,20 +862,18 @@ class PowerMaxMaskingTest(test.TestCase):
'_delete_cascaded_storage_groups') '_delete_cascaded_storage_groups')
@mock.patch.object(rest.PowerMaxRest, 'get_num_vols_in_sg', @mock.patch.object(rest.PowerMaxRest, 'get_num_vols_in_sg',
side_effect=[1, 3]) side_effect=[1, 3])
@mock.patch.object(rest.PowerMaxRest, 'delete_storage_group')
@mock.patch.object(masking.PowerMaxMasking, 'get_parent_sg_from_child', @mock.patch.object(masking.PowerMaxMasking, 'get_parent_sg_from_child',
side_effect=[None, 'parent_sg_name', 'parent_sg_name']) side_effect=[None, 'parent_sg_name', 'parent_sg_name'])
def test_last_vol_no_masking_views( def test_last_vol_no_masking_views(
self, mock_get_parent, mock_delete, mock_num_vols, self, mock_get_parent, mock_num_vols,
mock_delete_casc, mock_remove): mock_delete_casc, mock_remove):
for x in range(0, 3): for x in range(0, 3):
self.mask._last_vol_no_masking_views( self.mask._last_vol_no_masking_views(
self.data.array, self.data.storagegroup_name_i, self.data.array, self.data.storagegroup_name_i,
self.device_id, self.volume_name, self.extra_specs, self.device_id, self.volume_name, self.extra_specs,
False) False)
self.assertEqual(1, mock_delete.call_count)
self.assertEqual(1, mock_delete_casc.call_count) self.assertEqual(1, mock_delete_casc.call_count)
self.assertEqual(1, mock_remove.call_count) self.assertEqual(2, mock_remove.call_count)
@mock.patch.object(masking.PowerMaxMasking, @mock.patch.object(masking.PowerMaxMasking,
'_remove_last_vol_and_delete_sg') '_remove_last_vol_and_delete_sg')

View File

@ -179,6 +179,8 @@ class PowerMaxReplicationTest(test.TestCase):
'metro_hostlunid': 3} 'metro_hostlunid': 3}
self.assertEqual(ref_dict, info_dict) self.assertEqual(ref_dict, info_dict)
@mock.patch.object(rest.PowerMaxRest, 'get_ip_interface_physical_port',
return_value="FA-1D:1")
@mock.patch.object(rest.PowerMaxRest, 'get_iscsi_ip_address_and_iqn', @mock.patch.object(rest.PowerMaxRest, 'get_iscsi_ip_address_and_iqn',
return_value=([tpd.PowerMaxData.ip], return_value=([tpd.PowerMaxData.ip],
tpd.PowerMaxData.initiator)) tpd.PowerMaxData.initiator))
@ -187,7 +189,7 @@ class PowerMaxReplicationTest(test.TestCase):
@mock.patch.object(utils.PowerMaxUtils, 'is_metro_device', @mock.patch.object(utils.PowerMaxUtils, 'is_metro_device',
return_value=True) return_value=True)
def test_initialize_connection_vol_metro_iscsi(self, mock_md, mock_es, def test_initialize_connection_vol_metro_iscsi(self, mock_md, mock_es,
mock_ip): mock_ip, mock_dp):
metro_connector = deepcopy(self.data.connector) metro_connector = deepcopy(self.data.connector)
metro_connector['multipath'] = True metro_connector['multipath'] = True
phys_port = '%(dir)s:%(port)s' % { phys_port = '%(dir)s:%(port)s' % {

View File

@ -48,7 +48,8 @@ class PowerMaxRestTest(test.TestCase):
self.driver = driver self.driver = driver
self.common = self.driver.common self.common = self.driver.common
self.rest = self.common.rest self.rest = self.common.rest
self.mock_object(self.rest, 'is_snap_id', True) self.rest.is_snap_id = True
self.rest.u4p_version = rest.U4P_100_VERSION
self.utils = self.common.utils self.utils = self.common.utils
def test_rest_request_no_response(self): def test_rest_request_no_response(self):
@ -256,7 +257,7 @@ class PowerMaxRestTest(test.TestCase):
def test_get_uni_version_success(self): def test_get_uni_version_success(self):
ret_val = (200, tpd.PowerMaxData.version_details) ret_val = (200, tpd.PowerMaxData.version_details)
current_major_version = tpd.PowerMaxData.u4v_version current_major_version = tpd.PowerMaxData.u4p_version
with mock.patch.object(self.rest, 'request', return_value=ret_val): with mock.patch.object(self.rest, 'request', return_value=ret_val):
version, major_version = self.rest.get_uni_version() version, major_version = self.rest.get_uni_version()
self.assertIsNotNone(version) self.assertIsNotNone(version)
@ -417,7 +418,7 @@ class PowerMaxRestTest(test.TestCase):
}, },
"volume_size": 1, "volume_size": 1,
"capacityUnit": "GB"}]}}}}) "capacityUnit": "GB"}]}}}})
version = self.data.u4v_version endpoint_version = self.data.u4p_100_endpoint
with mock.patch.object(self.rest, 'modify_resource', with mock.patch.object(self.rest, 'modify_resource',
return_value=(200, return_value=(200,
return_message)) as mock_modify: return_message)) as mock_modify:
@ -425,7 +426,7 @@ class PowerMaxRestTest(test.TestCase):
array, storagegroup, payload) array, storagegroup, payload)
mock_modify.assert_called_once_with( mock_modify.assert_called_once_with(
self.data.array, 'sloprovisioning', 'storagegroup', self.data.array, 'sloprovisioning', 'storagegroup',
payload, version, resource_name=storagegroup) payload, endpoint_version, resource_name=storagegroup)
self.assertEqual(1, mock_modify.call_count) self.assertEqual(1, mock_modify.call_count)
self.assertEqual(200, status_code) self.assertEqual(200, status_code)
self.assertEqual(return_message, message) self.assertEqual(return_message, message)
@ -1902,6 +1903,14 @@ class PowerMaxRestTest(test.TestCase):
ucode = self.rest.get_array_ucode_version(array) ucode = self.rest.get_array_ucode_version(array)
self.assertEqual(self.data.powermax_model_details['ucode'], ucode) self.assertEqual(self.data.powermax_model_details['ucode'], ucode)
@mock.patch.object(rest.PowerMaxRest, 'get_array_detail',
return_value=tpd.PowerMaxData.powermax_model_100)
def test_get_array_microcode(self, mck_ucode):
array = self.data.array
microcode = self.rest.get_array_ucode_version(array)
self.assertEqual(self.data.powermax_model_100.get(
'microcode'), microcode)
def test_validate_unisphere_version_suceess(self): def test_validate_unisphere_version_suceess(self):
version = tpd.PowerMaxData.unisphere_version version = tpd.PowerMaxData.unisphere_version
returned_version = {'version': version} returned_version = {'version': version}
@ -1931,7 +1940,7 @@ class PowerMaxRestTest(test.TestCase):
valid_version = self.rest.validate_unisphere_version() valid_version = self.rest.validate_unisphere_version()
self.assertFalse(valid_version) self.assertFalse(valid_version)
request_count = mock_req.call_count request_count = mock_req.call_count
self.assertEqual(2, request_count) self.assertEqual(1, request_count)
@mock.patch.object(rest.PowerMaxRest, 'get_resource', @mock.patch.object(rest.PowerMaxRest, 'get_resource',
return_value=tpd.PowerMaxData.sg_rdf_group_details) return_value=tpd.PowerMaxData.sg_rdf_group_details)
@ -2390,7 +2399,7 @@ class PowerMaxRestTest(test.TestCase):
array_id, sg_name, rdf_group_no, rep_extra_specs) array_id, sg_name, rdf_group_no, rep_extra_specs)
def test_validate_unisphere_version_unofficial_success(self): def test_validate_unisphere_version_unofficial_success(self):
version = 'T9.2.0.1054' version = 'x10.0.0.425'
returned_version = {'version': version} returned_version = {'version': version}
with mock.patch.object(self.rest, "request", with mock.patch.object(self.rest, "request",
return_value=(200, return_value=(200,
@ -2402,6 +2411,7 @@ class PowerMaxRestTest(test.TestCase):
def test_validate_unisphere_version_unofficial_failure(self): def test_validate_unisphere_version_unofficial_failure(self):
version = 'T9.0.0.1054' version = 'T9.0.0.1054'
self.rest.u4p_version = 'T9.0.0.1054'
returned_version = {'version': version} returned_version = {'version': version}
with mock.patch.object(self.rest, "request", with mock.patch.object(self.rest, "request",
return_value=(200, return_value=(200,
@ -2410,7 +2420,7 @@ class PowerMaxRestTest(test.TestCase):
self.assertFalse(valid_version) self.assertFalse(valid_version)
def test_validate_unisphere_version_unofficial_greater_than(self): def test_validate_unisphere_version_unofficial_greater_than(self):
version = 'T9.2.0.1054' version = 'x10.0.0.425'
returned_version = {'version': version} returned_version = {'version': version}
with mock.patch.object(self.rest, "request", with mock.patch.object(self.rest, "request",
return_value=(200, return_value=(200,
@ -2449,7 +2459,7 @@ class PowerMaxRestTest(test.TestCase):
resource_name='test-sg') resource_name='test-sg')
expected_uri = ( expected_uri = (
'/%(ver)s/sloprovisioning/symmetrix/%(arr)s/storagegroup/test-sg' % '/%(ver)s/sloprovisioning/symmetrix/%(arr)s/storagegroup/test-sg' %
{'ver': rest.U4V_VERSION, 'arr': self.data.array}) {'ver': rest.U4P_100_VERSION, 'arr': self.data.array})
self.assertEqual(target_uri, expected_uri) self.assertEqual(target_uri, expected_uri)
def test_build_uri_kwargs_private_no_version(self): def test_build_uri_kwargs_private_no_version(self):
@ -2460,7 +2470,7 @@ class PowerMaxRestTest(test.TestCase):
def test_build_uri_kwargs_public_version(self): def test_build_uri_kwargs_public_version(self):
target_uri = self.rest._build_uri_kwargs(category='test') target_uri = self.rest._build_uri_kwargs(category='test')
expected_uri = '/%(ver)s/test' % {'ver': rest.U4V_VERSION} expected_uri = '/%(ver)s/test' % {'ver': rest.U4P_100_VERSION}
self.assertEqual(target_uri, expected_uri) self.assertEqual(target_uri, expected_uri)
def test_build_uri_kwargs_full_uri(self): def test_build_uri_kwargs_full_uri(self):
@ -2472,7 +2482,7 @@ class PowerMaxRestTest(test.TestCase):
object_type='obj', object_type_id='id4') object_type='obj', object_type_id='id4')
expected_uri = ( expected_uri = (
'/%(ver)s/test-cat/res-level/id1/res-type/id2/res/id3/obj/id4' % { '/%(ver)s/test-cat/res-level/id1/res-type/id2/res/id3/obj/id4' % {
'ver': rest.U4V_VERSION}) 'ver': rest.U4P_100_VERSION})
self.assertEqual(target_uri, expected_uri) self.assertEqual(target_uri, expected_uri)
@mock.patch.object( @mock.patch.object(
@ -2544,69 +2554,3 @@ class PowerMaxRestTest(test.TestCase):
self.data.array, self.data.device_id, self.data.array, self.data.device_id,
self.data.test_snapshot_snap_name) self.data.test_snapshot_snap_name)
self.assertEqual('0', snap_id) self.assertEqual('0', snap_id)
@mock.patch.object(
rest.PowerMaxRest, 'get_storage_group_list')
@mock.patch.object(
rest.PowerMaxRest, 'get_storage_group_rep',
side_effect=[{'rdf': False}, None])
def test_get_or_rename_storage_group_rep(
self, mock_sg_rep, mock_sg_list):
# Success - no need for rename
rep_info = self.rest.get_or_rename_storage_group_rep(
self.data.array, self.data.storagegroup_name_f,
self.data.extra_specs)
mock_sg_list.assert_not_called()
self.assertIsNotNone(rep_info)
# Fail - cannot find sg but no filter set
rep_info = self.rest.get_or_rename_storage_group_rep(
self.data.array, self.data.storagegroup_name_f,
self.data.extra_specs)
mock_sg_list.assert_not_called()
self.assertIsNone(rep_info)
@mock.patch.object(
rest.PowerMaxRest, '_rename_storage_group')
@mock.patch.object(
rest.PowerMaxRest, 'get_storage_group_list',
return_value=({'storageGroupId': ['user-name+uuid']}))
@mock.patch.object(
rest.PowerMaxRest, 'get_storage_group_rep',
side_effect=[None, ({'rdf': False}), ({'rdf': False})])
def test_get_or_rename_storage_group_rep_exists(
self, mock_sg_rep, mock_sg_list, mock_rename):
sg_filter = '<like>uuid'
rep_info = self.rest.get_or_rename_storage_group_rep(
self.data.array, self.data.storagegroup_name_f,
self.data.extra_specs, sg_filter=sg_filter)
mock_sg_list.assert_called_once_with(
self.data.array,
params={'storageGroupId': sg_filter})
group_list_return = {'storageGroupId': ['user-name+uuid']}
mock_rename.assert_called_once_with(
self.data.array,
group_list_return['storageGroupId'][0],
self.data.storagegroup_name_f,
self.data.extra_specs)
self.assertIsNotNone(rep_info)
@mock.patch.object(
rest.PowerMaxRest, '_rename_storage_group')
@mock.patch.object(
rest.PowerMaxRest, 'get_storage_group_list',
return_value=({'storageGroupId': ['user-name+uuid']}))
@mock.patch.object(
rest.PowerMaxRest, 'get_storage_group_rep',
side_effect=[None, None])
def test_get_or_rename_storage_group_rep_does_not_exist(
self, mock_sg_rep, mock_sg_list, mock_rename):
sg_filter = '<like>uuid'
rep_info = self.rest.get_or_rename_storage_group_rep(
self.data.array, self.data.storagegroup_name_f,
self.data.extra_specs, sg_filter=sg_filter)
mock_sg_list.assert_called_once_with(
self.data.array,
params={'storageGroupId': sg_filter})
mock_rename.assert_not_called()
self.assertIsNone(rep_info)

View File

@ -218,7 +218,6 @@ class PowerMaxCommon(object):
self._get_u4p_failover_info() self._get_u4p_failover_info()
self._gather_info() self._gather_info()
self._get_performance_config() self._get_performance_config()
self.rest.validate_unisphere_version()
def _gather_info(self): def _gather_info(self):
"""Gather the relevant information for update_volume_stats.""" """Gather the relevant information for update_volume_stats."""
@ -230,10 +229,13 @@ class PowerMaxCommon(object):
"configuration and note that the xml file is no " "configuration and note that the xml file is no "
"longer supported.") "longer supported.")
self.rest.set_rest_credentials(array_info) self.rest.set_rest_credentials(array_info)
self.rest.validate_unisphere_version()
if array_info: if array_info:
serial_number = array_info['SerialNumber'] serial_number = array_info['SerialNumber']
self.array_model, self.next_gen = ( self.array_model, self.next_gen = (
self.rest.get_array_model_info(serial_number)) self.rest.get_array_model_info(serial_number))
self.rest.set_residuals(serial_number)
self.ucode_level = self.rest.get_array_ucode_version(serial_number) self.ucode_level = self.rest.get_array_ucode_version(serial_number)
if self.replication_enabled: if self.replication_enabled:
if serial_number in self.replication_targets: if serial_number in self.replication_targets:
@ -857,9 +859,9 @@ class PowerMaxCommon(object):
mv_list, sg_list = ( mv_list, sg_list = (
self._get_mvs_and_sgs_from_volume( self._get_mvs_and_sgs_from_volume(
extra_specs[utils.ARRAY], extra_specs[utils.ARRAY],
device_info['device_id'])) device_info.get('device_id')))
self.volume_metadata.capture_detach_info( self.volume_metadata.capture_detach_info(
volume, extra_specs, device_info['device_id'], mv_list, volume, extra_specs, device_info.get('device_id'), mv_list,
sg_list) sg_list)
def _unmap_lun_promotion(self, volume, connector): def _unmap_lun_promotion(self, volume, connector):
@ -1270,12 +1272,12 @@ class PowerMaxCommon(object):
if rep_enabled: if rep_enabled:
__, r2_array = self.get_rdf_details(array, rep_config) __, r2_array = self.get_rdf_details(array, rep_config)
r2_ucode = self.rest.get_array_ucode_version(r2_array) r2_ucode = self.rest.get_array_ucode_version(r2_array)
if int(r1_ucode[2]) > utils.UCODE_5978_ELMSR: if self.utils.ode_capable(r1_ucode):
r1_ode_metro = True r1_ode_metro = True
r2_ucode = r2_ucode.split('.') r2_ucode = r2_ucode.split('.')
if self.rest.is_next_gen_array(r2_array): if self.rest.is_next_gen_array(r2_array):
r2_ode = True r2_ode = True
if int(r2_ucode[2]) > utils.UCODE_5978_ELMSR: if self.utils.ode_capable(r2_ucode):
r2_ode_metro = True r2_ode_metro = True
return r1_ode, r1_ode_metro, r2_ode, r2_ode_metro return r1_ode, r1_ode_metro, r2_ode, r2_ode_metro
@ -3006,6 +3008,15 @@ class PowerMaxCommon(object):
array, device_id) array, device_id)
if snapvx_src or snapvx_tgt: if snapvx_src or snapvx_tgt:
LOG.debug("Device %(dev)s is involved into a SnapVX session",
{'dev': device_id})
if snapvx_src:
LOG.debug("Device %(dev)s is the SnapVX source volume",
{'dev': device_id})
else:
LOG.debug("Device %(dev)s is the SnapVX target volume",
{'dev': device_id})
@coordination.synchronized("emc-source-{src_device_id}") @coordination.synchronized("emc-source-{src_device_id}")
def do_unlink_and_delete_snap(src_device_id): def do_unlink_and_delete_snap(src_device_id):
src_sessions, tgt_session = self.rest.find_snap_vx_sessions( src_sessions, tgt_session = self.rest.find_snap_vx_sessions(

View File

@ -1584,8 +1584,10 @@ class PowerMaxMasking(object):
self.add_volume_to_default_storage_group( self.add_volume_to_default_storage_group(
serial_number, device_id, volume_name, serial_number, device_id, volume_name,
extra_specs, src_sg=storagegroup_name) extra_specs, src_sg=storagegroup_name)
# Delete the storage group. # Remove last volume and delete the storage group.
self.rest.delete_storage_group(serial_number, storagegroup_name) self._remove_last_vol_and_delete_sg(
serial_number, device_id, volume_name,
storagegroup_name, extra_specs)
status = True status = True
else: else:
num_vols_parent = self.rest.get_num_vols_in_sg( num_vols_parent = self.rest.get_num_vols_in_sg(
@ -1716,10 +1718,20 @@ class PowerMaxMasking(object):
serial_number, device_id, "", serial_number, device_id, "",
extra_specs, src_sg=child_sg_name) extra_specs, src_sg=child_sg_name)
if child_sg_name != parent_sg_name: if child_sg_name != parent_sg_name:
self.rest.remove_child_sg_from_parent_sg(
serial_number, child_sg_name, parent_sg_name,
extra_specs)
self.rest.delete_storage_group(serial_number, parent_sg_name) self.rest.delete_storage_group(serial_number, parent_sg_name)
LOG.debug("Storage Group %(storagegroup_name)s " LOG.debug("Storage Group %(storagegroup_name)s "
"successfully deleted.", "successfully deleted.",
{'storagegroup_name': parent_sg_name}) {'storagegroup_name': parent_sg_name})
# Remove last volume and delete the storage group.
if self.rest.is_volume_in_storagegroup(
serial_number, device_id, child_sg_name):
self._remove_last_vol_and_delete_sg(
serial_number, device_id, 'last_vol',
child_sg_name, extra_specs)
else:
self.rest.delete_storage_group(serial_number, child_sg_name) self.rest.delete_storage_group(serial_number, child_sg_name)
LOG.debug("Storage Group %(storagegroup_name)s successfully deleted.", LOG.debug("Storage Group %(storagegroup_name)s successfully deleted.",

View File

@ -133,7 +133,8 @@ class PowerMaxVolumeMetadata(object):
self.version_dict['serial_number'] = serial_number self.version_dict['serial_number'] = serial_number
array_info_dict = self.rest.get_array_detail(serial_number) array_info_dict = self.rest.get_array_detail(serial_number)
self.version_dict['storage_firmware_version'] = ( self.version_dict['storage_firmware_version'] = (
array_info_dict['ucode']) array_info_dict.get(
'ucode', array_info_dict.get('microcode')))
self.version_dict['storage_model'] = array_info_dict['model'] self.version_dict['storage_model'] = array_info_dict['model']
self.version_dict['powermax_cinder_driver_version'] = ( self.version_dict['powermax_cinder_driver_version'] = (
self.powermax_driver_version) self.powermax_driver_version)

View File

@ -475,6 +475,16 @@ class PowerMaxProvision(object):
subscribed_capacity_gb = ( subscribed_capacity_gb = (
srp_capacity['subscribed_total_tb'] * units.Ki) srp_capacity['subscribed_total_tb'] * units.Ki)
array_reserve_percent = srp_details['reserved_cap_percent'] array_reserve_percent = srp_details['reserved_cap_percent']
except KeyError:
try:
srp_capacity = srp_details['fba_srp_capacity']
effective_capacity = srp_capacity['effective']
total_capacity_gb = effective_capacity['total_tb'] * units.Ki
remaining_capacity_gb = (
effective_capacity['free_tb'] * units.Ki)
array_reserve_percent = srp_details['reserved_cap_percent']
subscribed_capacity_gb = (
effective_capacity['used_tb'] * units.Ki)
except KeyError: except KeyError:
pass pass

View File

@ -36,8 +36,10 @@ LOG = logging.getLogger(__name__)
SLOPROVISIONING = 'sloprovisioning' SLOPROVISIONING = 'sloprovisioning'
REPLICATION = 'replication' REPLICATION = 'replication'
SYSTEM = 'system' SYSTEM = 'system'
U4V_VERSION = '92' U4P_100_VERSION = '100'
MIN_U4P_VERSION = '9.2.0.0' MIN_U4P_100_VERSION = '10.0.0.0'
U4P_92_VERSION = '92'
MIN_U4P_92_VERSION = '9.2.0.0'
UCODE_5978 = '5978' UCODE_5978 = '5978'
retry_exc_tuple = (exception.VolumeBackendAPIException,) retry_exc_tuple = (exception.VolumeBackendAPIException,)
u4p_failover_max_wait = 120 u4p_failover_max_wait = 120
@ -60,6 +62,11 @@ SUCCEEDED = 'succeeded'
CREATE_VOL_STRING = "Creating new Volumes" CREATE_VOL_STRING = "Creating new Volumes"
POPULATE_SG_LIST = "Populating Storage Group(s) with volumes" POPULATE_SG_LIST = "Populating Storage Group(s) with volumes"
# Sequence of beta microcode (in order)
DEV_CODE = 'x'
TEST_CODE = 't'
QUAL_CODE = 'v'
class PowerMaxRest(object): class PowerMaxRest(object):
"""Rest class based on Unisphere for PowerMax Rest API.""" """Rest class based on Unisphere for PowerMax Rest API."""
@ -85,6 +92,7 @@ class PowerMaxRest(object):
self.ucode_major_level = None self.ucode_major_level = None
self.ucode_minor_level = None self.ucode_minor_level = None
self.is_snap_id = False self.is_snap_id = False
self.u4p_version = None
def set_rest_credentials(self, array_info): def set_rest_credentials(self, array_info):
"""Given the array record set the rest server credentials. """Given the array record set the rest server credentials.
@ -100,12 +108,18 @@ class PowerMaxRest(object):
self.base_uri = ("https://%(ip_port)s/univmax/restapi" % { self.base_uri = ("https://%(ip_port)s/univmax/restapi" % {
'ip_port': ip_port}) 'ip_port': ip_port})
self.session = self._establish_rest_session() self.session = self._establish_rest_session()
def set_residuals(self, serial_number):
"""Set ucode and snapid information.
:param serial_number: the array serial number
"""
self.ucode_major_level, self.ucode_minor_level = ( self.ucode_major_level, self.ucode_minor_level = (
self.get_major_minor_ucode(array_info['SerialNumber'])) self.get_major_minor_ucode(serial_number))
self.is_snap_id = self._is_snapid_enabled() self.is_snap_id = self._is_snapid_enabled()
def set_u4p_failover_config(self, failover_info): def set_u4p_failover_config(self, failover_info):
"""Set the environment failover Unisphere targets and configuration.. """Set the environment failover Unisphere targets and configuration.
:param failover_info: failover target record :param failover_info: failover target record
""" """
@ -372,7 +386,7 @@ class PowerMaxRest(object):
rc -- int, status -- string, task -- list of dicts rc -- int, status -- string, task -- list of dicts
""" """
complete, rc, status, result, task = False, 0, None, None, None complete, rc, status, result, task = False, 0, None, None, None
job_url = "/%s/system/job/%s" % (U4V_VERSION, job_id) job_url = "/%s/system/job/%s" % (self.u4p_version, job_id)
job = self.get_request(job_url, 'job') job = self.get_request(job_url, 'job')
if job: if job:
status = job['status'] status = job['status']
@ -448,8 +462,7 @@ class PowerMaxRest(object):
return target_uri return target_uri
@staticmethod def _build_uri_legacy_args(self, *args, **kwargs):
def _build_uri_legacy_args(*args, **kwargs):
"""Build the target URI using legacy args & kwargs. """Build the target URI using legacy args & kwargs.
Expected format: Expected format:
@ -458,7 +471,7 @@ class PowerMaxRest(object):
arg[2]: the resource type e.g. 'maskingview' -- str arg[2]: the resource type e.g. 'maskingview' -- str
kwarg resource_name: the name of a specific resource -- str kwarg resource_name: the name of a specific resource -- str
kwarg private: if endpoint is private -- bool kwarg private: if endpoint is private -- bool
kwarg version: U4V REST endpoint version -- int/str kwarg version: U4P REST endpoint version -- int/str
kwarg no_version: if endpoint should be versionless -- bool kwarg no_version: if endpoint should be versionless -- bool
:param args: input args -- see above :param args: input args -- see above
@ -470,7 +483,7 @@ class PowerMaxRest(object):
# Extract keyword args following legacy _build_uri() format # Extract keyword args following legacy _build_uri() format
resource_name = kwargs.get('resource_name') resource_name = kwargs.get('resource_name')
private = kwargs.get('private') private = kwargs.get('private')
version = kwargs.get('version', U4V_VERSION) version = kwargs.get('version', self.u4p_version)
if kwargs.get('no_version'): if kwargs.get('no_version'):
version = None version = None
@ -489,8 +502,7 @@ class PowerMaxRest(object):
return target_uri return target_uri
@staticmethod def _build_uri_kwargs(self, **kwargs):
def _build_uri_kwargs(**kwargs):
"""Build the target URI using kwargs. """Build the target URI using kwargs.
Expected kwargs: Expected kwargs:
@ -517,7 +529,7 @@ class PowerMaxRest(object):
:param kwargs: input keyword args -- see above :param kwargs: input keyword args -- see above
:return: target URI -- str :return: target URI -- str
""" """
version = kwargs.get('version', U4V_VERSION) version = kwargs.get('version', self.u4p_version)
if kwargs.get('no_version'): if kwargs.get('no_version'):
version = None version = None
@ -607,7 +619,7 @@ class PowerMaxRest(object):
def get_resource(self, array, category, resource_type, def get_resource(self, array, category, resource_type,
resource_name=None, params=None, private=False, resource_name=None, params=None, private=False,
version=U4V_VERSION): version=None):
"""Get resource details from array. """Get resource details from array.
:param array: the array serial number :param array: the array serial number
@ -621,7 +633,7 @@ class PowerMaxRest(object):
""" """
target_uri = self.build_uri( target_uri = self.build_uri(
array, category, resource_type, resource_name=resource_name, array, category, resource_type, resource_name=resource_name,
private=private, version=version) private=private, version=self.u4p_version)
return self.get_request(target_uri, resource_type, params) return self.get_request(target_uri, resource_type, params)
def create_resource(self, array, category, resource_type, payload, def create_resource(self, array, category, resource_type, payload,
@ -645,7 +657,7 @@ class PowerMaxRest(object):
return status_code, message return status_code, message
def modify_resource( def modify_resource(
self, array, category, resource_type, payload, version=U4V_VERSION, self, array, category, resource_type, payload, version=None,
resource_name=None, private=False): resource_name=None, private=False):
"""Modify a resource. """Modify a resource.
@ -660,7 +672,7 @@ class PowerMaxRest(object):
""" """
target_uri = self.build_uri( target_uri = self.build_uri(
array, category, resource_type, resource_name=resource_name, array, category, resource_type, resource_name=resource_name,
private=private, version=version) private=private, version=self.u4p_version)
status_code, message = self.request(target_uri, PUT, status_code, message = self.request(target_uri, PUT,
request_object=payload) request_object=payload)
operation = 'modify %(res)s resource' % {'res': resource_type} operation = 'modify %(res)s resource' % {'res': resource_type}
@ -695,7 +707,7 @@ class PowerMaxRest(object):
:returns arrays -- list :returns arrays -- list
""" """
target_uri = '/%s/sloprovisioning/symmetrix' % U4V_VERSION target_uri = '/%s/sloprovisioning/symmetrix' % self.u4p_version
array_details = self.get_request(target_uri, 'sloprovisioning') array_details = self.get_request(target_uri, 'sloprovisioning')
if not array_details: if not array_details:
LOG.error("Could not get array details from Unisphere instance.") LOG.error("Could not get array details from Unisphere instance.")
@ -708,7 +720,7 @@ class PowerMaxRest(object):
:param array: the array serial number :param array: the array serial number
:returns: array_details -- dict or None :returns: array_details -- dict or None
""" """
target_uri = '/%s/system/symmetrix/%s' % (U4V_VERSION, array) target_uri = '/%s/system/symmetrix/%s' % (self.u4p_version, array)
array_details = self.get_request(target_uri, 'system') array_details = self.get_request(target_uri, 'system')
if not array_details: if not array_details:
LOG.error("Cannot connect to array %(array)s.", LOG.error("Cannot connect to array %(array)s.",
@ -721,7 +733,8 @@ class PowerMaxRest(object):
:param array: the array serial number :param array: the array serial number
:returns: tag list -- list or empty list :returns: tag list -- list or empty list
""" """
target_uri = '/%s/system/tag?array_id=%s' % (U4V_VERSION, array) target_uri = '/%s/system/tag?array_id=%s' % (
self.u4p_version, array)
array_tags = self.get_request(target_uri, 'system') array_tags = self.get_request(target_uri, 'system')
return array_tags.get('tag_name') return array_tags.get('tag_name')
@ -734,7 +747,8 @@ class PowerMaxRest(object):
is_next_gen = False is_next_gen = False
array_details = self.get_array_detail(array) array_details = self.get_array_detail(array)
if array_details: if array_details:
ucode_version = array_details['ucode'].split('.')[0] ucode = array_details.get('ucode', array_details.get('microcode'))
ucode_version = ucode.split('.')[0] if ucode else None
if ucode_version >= UCODE_5978: if ucode_version >= UCODE_5978:
is_next_gen = True is_next_gen = True
return is_next_gen return is_next_gen
@ -749,7 +763,7 @@ class PowerMaxRest(object):
if response and response.get('version'): if response and response.get('version'):
version = response['version'] version = response['version']
version_list = version.split('.') version_list = version.split('.')
major_version = version_list[0][1] + version_list[1] major_version = version_list[0][1:] + version_list[1]
return version, major_version return version, major_version
def get_unisphere_version(self): def get_unisphere_version(self):
@ -757,12 +771,8 @@ class PowerMaxRest(object):
:returns: version dict :returns: version dict
""" """
post_90_endpoint = '/version' version_endpoint = '/version'
pre_91_endpoint = '/system/version' status_code, version_dict = self.request(version_endpoint, GET)
status_code, version_dict = self.request(post_90_endpoint, GET)
if status_code is not STATUS_200:
status_code, version_dict = self.request(pre_91_endpoint, GET)
if not version_dict: if not version_dict:
LOG.error("Unisphere version info not found.") LOG.error("Unisphere version info not found.")
@ -844,7 +854,8 @@ class PowerMaxRest(object):
if system_info and system_info.get('model'): if system_info and system_info.get('model'):
array_model = system_info.get('model') array_model = system_info.get('model')
if system_info: if system_info:
ucode_version = system_info['ucode'].split('.')[0] ucode = system_info.get('ucode', system_info.get('microcode'))
ucode_version = ucode.split('.')[0]
if ucode_version >= UCODE_5978: if ucode_version >= UCODE_5978:
is_next_gen = True is_next_gen = True
return array_model, is_next_gen return array_model, is_next_gen
@ -858,7 +869,8 @@ class PowerMaxRest(object):
ucode_version = None ucode_version = None
system_info = self.get_array_detail(array) system_info = self.get_array_detail(array)
if system_info: if system_info:
ucode_version = system_info['ucode'] ucode_version = system_info.get(
'ucode', system_info.get('microcode'))
return ucode_version return ucode_version
def is_compression_capable(self, array): def is_compression_capable(self, array):
@ -869,7 +881,7 @@ class PowerMaxRest(object):
""" """
is_compression_capable = False is_compression_capable = False
target_uri = ("/%s/sloprovisioning/symmetrix?compressionCapable=true" target_uri = ("/%s/sloprovisioning/symmetrix?compressionCapable=true"
% U4V_VERSION) % self.u4p_version)
status_code, message = self.request(target_uri, GET) status_code, message = self.request(target_uri, GET)
self.check_status_code_success( self.check_status_code_success(
"Check if compression enabled", status_code, message) "Check if compression enabled", status_code, message)
@ -1018,7 +1030,7 @@ class PowerMaxRest(object):
return storagegroup_name return storagegroup_name
def modify_storage_group(self, array, storagegroup, payload, def modify_storage_group(self, array, storagegroup, payload,
version=U4V_VERSION): version=None):
"""Modify a storage group (PUT operation). """Modify a storage group (PUT operation).
:param version: the uv4 version :param version: the uv4 version
@ -1028,8 +1040,8 @@ class PowerMaxRest(object):
:returns: status_code -- int, message -- string, server response :returns: status_code -- int, message -- string, server response
""" """
return self.modify_resource( return self.modify_resource(
array, SLOPROVISIONING, 'storagegroup', payload, version, array, SLOPROVISIONING, 'storagegroup', payload,
resource_name=storagegroup) self.u4p_version, resource_name=storagegroup)
def modify_storage_array(self, array, payload): def modify_storage_array(self, array, payload):
"""Modify a storage array (PUT operation). """Modify a storage array (PUT operation).
@ -1038,7 +1050,8 @@ class PowerMaxRest(object):
:param payload: the request payload :param payload: the request payload
:returns: status_code -- int, message -- string, server response :returns: status_code -- int, message -- string, server response
""" """
target_uri = '/%s/sloprovisioning/symmetrix/%s' % (U4V_VERSION, array) target_uri = '/%s/sloprovisioning/symmetrix/%s' % (
self.u4p_version, array)
status_code, message = self.request(target_uri, PUT, status_code, message = self.request(target_uri, PUT,
request_object=payload) request_object=payload)
operation = 'modify %(res)s resource' % {'res': 'symmetrix'} operation = 'modify %(res)s resource' % {'res': 'symmetrix'}
@ -1417,13 +1430,8 @@ class PowerMaxRest(object):
:returns: volume dict :returns: volume dict
:raises: VolumeBackendAPIException :raises: VolumeBackendAPIException
""" """
if not device_id:
LOG.warning('No device id supplied to get_volume.')
return dict()
version = self.get_uni_version()[1]
volume_dict = self.get_resource( volume_dict = self.get_resource(
array, SLOPROVISIONING, 'volume', resource_name=device_id, array, SLOPROVISIONING, 'volume', resource_name=device_id)
version=version)
if not volume_dict: if not volume_dict:
exception_message = (_("Volume %(deviceID)s not found.") exception_message = (_("Volume %(deviceID)s not found.")
% {'deviceID': device_id}) % {'deviceID': device_id})
@ -1570,7 +1578,8 @@ class PowerMaxRest(object):
raise exception.VolumeBackendAPIException(exception_message) raise exception.VolumeBackendAPIException(exception_message)
if ((self.ucode_major_level >= utils.UCODE_5978) if ((self.ucode_major_level >= utils.UCODE_5978)
and (self.ucode_minor_level > utils.UCODE_5978_ELMSR)): and (self.ucode_minor_level > utils.UCODE_5978_ELMSR) or (
self.ucode_major_level >= utils.UCODE_6079)):
# Use Rapid TDEV Deallocation to delete after ELMSR # Use Rapid TDEV Deallocation to delete after ELMSR
try: try:
self.delete_resource(array, SLOPROVISIONING, self.delete_resource(array, SLOPROVISIONING,
@ -1807,13 +1816,23 @@ class PowerMaxRest(object):
:param ip_address: the ip address associated with the port -- str :param ip_address: the ip address associated with the port -- str
:returns: physical director:port -- str :returns: physical director:port -- str
""" """
if self.u4p_version == U4P_100_VERSION:
target_key = 'iscsi_endpoint'
elif self.u4p_version == U4P_92_VERSION:
target_key = 'iscsi_target'
else:
msg = (_(
"Unable to determine the target_key for version %(ver)s." % {
'ver': self.u4p_version}
))
LOG.error(msg)
raise exception.VolumeBackendAPIException(message=msg)
director_id = virtual_port.split(':')[0] director_id = virtual_port.split(':')[0]
params = {'ip_list': ip_address, 'iscsi_target': False} params = {'ip_list': ip_address, target_key: False}
target_uri = self.build_uri( target_uri = self.build_uri(
category=SYSTEM, resource_level='symmetrix', category=SYSTEM, resource_level='symmetrix',
resource_level_id=array_id, resource_type='director', resource_level_id=array_id, resource_type='director',
resource_type_id=director_id, resource='port') resource_type_id=director_id, resource='port')
port_info = self.get_request( port_info = self.get_request(
target_uri, 'port IP interface', params) target_uri, 'port IP interface', params)
if not port_info: if not port_info:
@ -2110,7 +2129,7 @@ class PowerMaxRest(object):
""" """
array_capabilities = None array_capabilities = None
target_uri = ("/%s/replication/capabilities/symmetrix" target_uri = ("/%s/replication/capabilities/symmetrix"
% U4V_VERSION) % self.u4p_version)
capabilities = self.get_request( capabilities = self.get_request(
target_uri, 'replication capabilities') target_uri, 'replication capabilities')
if capabilities: if capabilities:
@ -3449,19 +3468,25 @@ class PowerMaxRest(object):
:returns: unisphere_meets_min_req -- boolean :returns: unisphere_meets_min_req -- boolean
""" """
running_version, major_version = self.get_uni_version() running_version, major_version = self.get_uni_version()
minimum_version = MIN_U4P_VERSION if major_version == U4P_100_VERSION:
self.u4p_version = U4P_100_VERSION
minimum_version = MIN_U4P_100_VERSION
elif major_version:
self.u4p_version = U4P_92_VERSION
minimum_version = MIN_U4P_92_VERSION
unisphere_meets_min_req = False unisphere_meets_min_req = False
if running_version and (running_version[0].isalpha()): if running_version and (running_version[0].isalpha()):
# remove leading letter # remove leading letter
if running_version.lower()[0] == 'v': if running_version.lower()[0] == QUAL_CODE:
version = running_version[1:] version = running_version[1:]
unisphere_meets_min_req = ( unisphere_meets_min_req = (
self.utils.version_meet_req(version, minimum_version)) self.utils.version_meet_req(version, minimum_version))
elif running_version.lower()[0] == 't': elif running_version.lower()[0] == TEST_CODE or (
running_version.lower()[0] == DEV_CODE):
LOG.warning("%(version)s This is not a official release of " LOG.warning("%(version)s This is not a official release of "
"Unisphere.", {'version': running_version}) "Unisphere.", {'version': running_version})
return major_version >= U4V_VERSION return int(major_version) >= int(self.u4p_version)
if unisphere_meets_min_req: if unisphere_meets_min_req:
LOG.info("Unisphere version %(running_version)s meets minimum " LOG.info("Unisphere version %(running_version)s meets minimum "
@ -3522,7 +3547,8 @@ class PowerMaxRest(object):
ucode_minor_level = 0 ucode_minor_level = 0
if array_details: if array_details:
split_ucode_level = array_details['ucode'].split('.') ucode = array_details.get('ucode', array_details.get('microcode'))
split_ucode_level = ucode.split('.')
ucode_level = [int(level) for level in split_ucode_level] ucode_level = [int(level) for level in split_ucode_level]
ucode_major_level = ucode_level[0] ucode_major_level = ucode_level[0]
ucode_minor_level = ucode_level[1] ucode_minor_level = ucode_level[1]
@ -3534,21 +3560,5 @@ class PowerMaxRest(object):
:returns: boolean :returns: boolean
""" """
return (self.ucode_major_level >= utils.UCODE_5978 and return (self.ucode_major_level >= utils.UCODE_5978 and
self.ucode_minor_level >= utils.UCODE_5978_HICKORY) self.ucode_minor_level >= utils.UCODE_5978_HICKORY) or (
self.ucode_major_level >= utils.UCODE_6079)
def _rename_storage_group(
self, array, old_name, new_name, extra_specs):
"""Rename the storage group.
:param array: the array serial number
:param old_name: the original name
:param new_name: the new name
:param extra_specs: the extra specifications
"""
payload = {"editStorageGroupActionParam": {
"renameStorageGroupParam": {
"new_storage_Group_name": new_name}}}
status_code, job = self.modify_storage_group(
array, old_name, payload)
self.wait_for_job(
'Rename storage group', status_code, job, extra_specs)

View File

@ -45,6 +45,7 @@ TRUNCATE_27 = 27
UCODE_5978_ELMSR = 221 UCODE_5978_ELMSR = 221
UCODE_5978_HICKORY = 660 UCODE_5978_HICKORY = 660
UCODE_5978 = 5978 UCODE_5978 = 5978
UCODE_6079 = 6079
UPPER_HOST_CHARS = 16 UPPER_HOST_CHARS = 16
UPPER_PORT_GROUP_CHARS = 12 UPPER_PORT_GROUP_CHARS = 12
@ -140,7 +141,8 @@ PORT_GROUP_LABEL = 'port_group_label'
# Array Models, Service Levels & Workloads # Array Models, Service Levels & Workloads
VMAX_HYBRID_MODELS = ['VMAX100K', 'VMAX200K', 'VMAX400K'] VMAX_HYBRID_MODELS = ['VMAX100K', 'VMAX200K', 'VMAX400K']
VMAX_AFA_MODELS = ['VMAX250F', 'VMAX450F', 'VMAX850F', 'VMAX950F'] VMAX_AFA_MODELS = ['VMAX250F', 'VMAX450F', 'VMAX850F', 'VMAX950F']
PMAX_MODELS = ['PowerMax_2000', 'PowerMax_8000'] PMAX_MODELS = ['PowerMax_2000', 'PowerMax_8000', 'PowerMax_8500',
'PowerMax_2500']
HYBRID_SLS = ['Diamond', 'Platinum', 'Gold', 'Silver', 'Bronze', 'Optimized', HYBRID_SLS = ['Diamond', 'Platinum', 'Gold', 'Silver', 'Bronze', 'Optimized',
'None', 'NONE'] 'None', 'NONE']
@ -2124,3 +2126,14 @@ class PowerMaxUtils(object):
:returns: str :returns: str
""" """
return in_value if isinstance(in_value, str) else str(in_value) return in_value if isinstance(in_value, str) else str(in_value)
@staticmethod
def ode_capable(in_value):
"""Check if online device expansion capable
:param in_value: microcode
:returns: Boolean
"""
return ((int(in_value[0]) == UCODE_5978 and
int(in_value[2]) > UCODE_5978_ELMSR) or
(int(in_value[0]) > UCODE_5978))

View File

@ -0,0 +1,4 @@
---
features:
- |
Dell PowerMax driver now supports Unisphere for PowerMax 10.0