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:
parent
acb8711dcf
commit
567f2a1b08
@ -79,7 +79,8 @@ class PowerMaxData(object):
|
||||
rdf_group_no_2 = '71'
|
||||
rdf_group_no_3 = '72'
|
||||
rdf_group_no_4 = '73'
|
||||
u4v_version = '92'
|
||||
u4p_version = '92'
|
||||
u4p_100_endpoint = '100'
|
||||
storagegroup_name_source = 'Grp_source_sg'
|
||||
storagegroup_name_target = 'Grp_target_sg'
|
||||
group_snapshot_name = 'Grp_snapshot'
|
||||
@ -944,6 +945,9 @@ class PowerMaxData(object):
|
||||
powermax_model_details = {'symmetrixId': array,
|
||||
'model': 'PowerMax_2000',
|
||||
'ucode': '5978.1091.1092'}
|
||||
powermax_model_100 = {'symmetrixId': array,
|
||||
'model': 'PowerMax_2500',
|
||||
'microcode': '6079.65.0'}
|
||||
vmax_slo_details = {'sloId': ['Diamond', 'Optimized']}
|
||||
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'
|
||||
unisphere_version = u'V9.2.0.0'
|
||||
unisphere_version_90 = "V9.0.0.1"
|
||||
unisphere_version_100 = "V10.0.0.0"
|
||||
openstack_release = '12.0.0.0b3.dev401'
|
||||
openstack_version = '12.0.0'
|
||||
python_version = '2.7.12'
|
||||
|
@ -1746,7 +1746,7 @@ class PowerMaxCommonTest(test.TestCase):
|
||||
@mock.patch.object(common.PowerMaxCommon,
|
||||
'_get_target_wwns_from_masking_view')
|
||||
@mock.patch.object(utils.PowerMaxUtils, 'get_host_name_label',
|
||||
return_value = 'my_short_h94485')
|
||||
return_value='my_short_h94485')
|
||||
@mock.patch.object(utils.PowerMaxUtils, 'is_replication_enabled',
|
||||
return_value=False)
|
||||
def test_get_target_wwns_host_override(
|
||||
@ -1802,6 +1802,7 @@ class PowerMaxCommonTest(test.TestCase):
|
||||
array, portgroup_name, initiator_group_name)
|
||||
|
||||
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,
|
||||
'port': self.data.iscsi_port}
|
||||
ref_ip_iqn = [{'iqn': self.data.initiator,
|
||||
@ -1817,6 +1818,7 @@ class PowerMaxCommonTest(test.TestCase):
|
||||
self.assertEqual(ref_ip_iqn, ip_iqn_list)
|
||||
|
||||
def test_find_ip_and_iqns(self):
|
||||
self.common.rest.u4p_version = self.data.u4p_100_endpoint
|
||||
ref_ip_iqn = [{'iqn': self.data.initiator,
|
||||
'ip': self.data.ip,
|
||||
'physical_port': self.data.iscsi_dir_port}]
|
||||
|
@ -99,6 +99,7 @@ class PowerMaxISCSITest(test.TestCase):
|
||||
'iqn': self.data.initiator,
|
||||
'physical_port': phys_port}],
|
||||
'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.common, 'get_port_group_from_masking_view',
|
||||
@ -109,6 +110,7 @@ class PowerMaxISCSITest(test.TestCase):
|
||||
ref_dict, self.data.test_volume)
|
||||
|
||||
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(
|
||||
self.data.array, self.data.port_group_name_i)
|
||||
host_lun_id = self.data.iscsi_device_info['hostlunid']
|
||||
@ -131,6 +133,7 @@ class PowerMaxISCSITest(test.TestCase):
|
||||
device_info, self.data.test_volume)
|
||||
|
||||
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(
|
||||
self.data.array, self.data.port_group_name_i)
|
||||
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):
|
||||
vol = deepcopy(self.data.test_volume)
|
||||
self.common.rest.u4p_version = self.data.u4p_version
|
||||
ip_and_iqn = self.common._find_ip_and_iqns(
|
||||
self.data.array, self.data.port_group_name_i)
|
||||
host_lun_id = self.data.iscsi_device_info['hostlunid']
|
||||
|
@ -862,20 +862,18 @@ class PowerMaxMaskingTest(test.TestCase):
|
||||
'_delete_cascaded_storage_groups')
|
||||
@mock.patch.object(rest.PowerMaxRest, 'get_num_vols_in_sg',
|
||||
side_effect=[1, 3])
|
||||
@mock.patch.object(rest.PowerMaxRest, 'delete_storage_group')
|
||||
@mock.patch.object(masking.PowerMaxMasking, 'get_parent_sg_from_child',
|
||||
side_effect=[None, 'parent_sg_name', 'parent_sg_name'])
|
||||
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):
|
||||
for x in range(0, 3):
|
||||
self.mask._last_vol_no_masking_views(
|
||||
self.data.array, self.data.storagegroup_name_i,
|
||||
self.device_id, self.volume_name, self.extra_specs,
|
||||
False)
|
||||
self.assertEqual(1, mock_delete.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,
|
||||
'_remove_last_vol_and_delete_sg')
|
||||
|
@ -179,6 +179,8 @@ class PowerMaxReplicationTest(test.TestCase):
|
||||
'metro_hostlunid': 3}
|
||||
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',
|
||||
return_value=([tpd.PowerMaxData.ip],
|
||||
tpd.PowerMaxData.initiator))
|
||||
@ -187,7 +189,7 @@ class PowerMaxReplicationTest(test.TestCase):
|
||||
@mock.patch.object(utils.PowerMaxUtils, 'is_metro_device',
|
||||
return_value=True)
|
||||
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['multipath'] = True
|
||||
phys_port = '%(dir)s:%(port)s' % {
|
||||
|
@ -48,7 +48,8 @@ class PowerMaxRestTest(test.TestCase):
|
||||
self.driver = driver
|
||||
self.common = self.driver.common
|
||||
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
|
||||
|
||||
def test_rest_request_no_response(self):
|
||||
@ -256,7 +257,7 @@ class PowerMaxRestTest(test.TestCase):
|
||||
|
||||
def test_get_uni_version_success(self):
|
||||
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):
|
||||
version, major_version = self.rest.get_uni_version()
|
||||
self.assertIsNotNone(version)
|
||||
@ -417,7 +418,7 @@ class PowerMaxRestTest(test.TestCase):
|
||||
},
|
||||
"volume_size": 1,
|
||||
"capacityUnit": "GB"}]}}}})
|
||||
version = self.data.u4v_version
|
||||
endpoint_version = self.data.u4p_100_endpoint
|
||||
with mock.patch.object(self.rest, 'modify_resource',
|
||||
return_value=(200,
|
||||
return_message)) as mock_modify:
|
||||
@ -425,7 +426,7 @@ class PowerMaxRestTest(test.TestCase):
|
||||
array, storagegroup, payload)
|
||||
mock_modify.assert_called_once_with(
|
||||
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(200, status_code)
|
||||
self.assertEqual(return_message, message)
|
||||
@ -1902,6 +1903,14 @@ class PowerMaxRestTest(test.TestCase):
|
||||
ucode = self.rest.get_array_ucode_version(array)
|
||||
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):
|
||||
version = tpd.PowerMaxData.unisphere_version
|
||||
returned_version = {'version': version}
|
||||
@ -1931,7 +1940,7 @@ class PowerMaxRestTest(test.TestCase):
|
||||
valid_version = self.rest.validate_unisphere_version()
|
||||
self.assertFalse(valid_version)
|
||||
request_count = mock_req.call_count
|
||||
self.assertEqual(2, request_count)
|
||||
self.assertEqual(1, request_count)
|
||||
|
||||
@mock.patch.object(rest.PowerMaxRest, 'get_resource',
|
||||
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)
|
||||
|
||||
def test_validate_unisphere_version_unofficial_success(self):
|
||||
version = 'T9.2.0.1054'
|
||||
version = 'x10.0.0.425'
|
||||
returned_version = {'version': version}
|
||||
with mock.patch.object(self.rest, "request",
|
||||
return_value=(200,
|
||||
@ -2402,6 +2411,7 @@ class PowerMaxRestTest(test.TestCase):
|
||||
|
||||
def test_validate_unisphere_version_unofficial_failure(self):
|
||||
version = 'T9.0.0.1054'
|
||||
self.rest.u4p_version = 'T9.0.0.1054'
|
||||
returned_version = {'version': version}
|
||||
with mock.patch.object(self.rest, "request",
|
||||
return_value=(200,
|
||||
@ -2410,7 +2420,7 @@ class PowerMaxRestTest(test.TestCase):
|
||||
self.assertFalse(valid_version)
|
||||
|
||||
def test_validate_unisphere_version_unofficial_greater_than(self):
|
||||
version = 'T9.2.0.1054'
|
||||
version = 'x10.0.0.425'
|
||||
returned_version = {'version': version}
|
||||
with mock.patch.object(self.rest, "request",
|
||||
return_value=(200,
|
||||
@ -2449,7 +2459,7 @@ class PowerMaxRestTest(test.TestCase):
|
||||
resource_name='test-sg')
|
||||
expected_uri = (
|
||||
'/%(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)
|
||||
|
||||
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):
|
||||
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)
|
||||
|
||||
def test_build_uri_kwargs_full_uri(self):
|
||||
@ -2472,7 +2482,7 @@ class PowerMaxRestTest(test.TestCase):
|
||||
object_type='obj', object_type_id='id4')
|
||||
expected_uri = (
|
||||
'/%(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)
|
||||
|
||||
@mock.patch.object(
|
||||
@ -2544,69 +2554,3 @@ class PowerMaxRestTest(test.TestCase):
|
||||
self.data.array, self.data.device_id,
|
||||
self.data.test_snapshot_snap_name)
|
||||
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)
|
||||
|
@ -218,7 +218,6 @@ class PowerMaxCommon(object):
|
||||
self._get_u4p_failover_info()
|
||||
self._gather_info()
|
||||
self._get_performance_config()
|
||||
self.rest.validate_unisphere_version()
|
||||
|
||||
def _gather_info(self):
|
||||
"""Gather the relevant information for update_volume_stats."""
|
||||
@ -230,10 +229,13 @@ class PowerMaxCommon(object):
|
||||
"configuration and note that the xml file is no "
|
||||
"longer supported.")
|
||||
self.rest.set_rest_credentials(array_info)
|
||||
self.rest.validate_unisphere_version()
|
||||
|
||||
if array_info:
|
||||
serial_number = array_info['SerialNumber']
|
||||
self.array_model, self.next_gen = (
|
||||
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)
|
||||
if self.replication_enabled:
|
||||
if serial_number in self.replication_targets:
|
||||
@ -857,9 +859,9 @@ class PowerMaxCommon(object):
|
||||
mv_list, sg_list = (
|
||||
self._get_mvs_and_sgs_from_volume(
|
||||
extra_specs[utils.ARRAY],
|
||||
device_info['device_id']))
|
||||
device_info.get('device_id')))
|
||||
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)
|
||||
|
||||
def _unmap_lun_promotion(self, volume, connector):
|
||||
@ -1270,12 +1272,12 @@ class PowerMaxCommon(object):
|
||||
if rep_enabled:
|
||||
__, r2_array = self.get_rdf_details(array, rep_config)
|
||||
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
|
||||
r2_ucode = r2_ucode.split('.')
|
||||
if self.rest.is_next_gen_array(r2_array):
|
||||
r2_ode = True
|
||||
if int(r2_ucode[2]) > utils.UCODE_5978_ELMSR:
|
||||
if self.utils.ode_capable(r2_ucode):
|
||||
r2_ode_metro = True
|
||||
|
||||
return r1_ode, r1_ode_metro, r2_ode, r2_ode_metro
|
||||
@ -3006,6 +3008,15 @@ class PowerMaxCommon(object):
|
||||
array, device_id)
|
||||
|
||||
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}")
|
||||
def do_unlink_and_delete_snap(src_device_id):
|
||||
src_sessions, tgt_session = self.rest.find_snap_vx_sessions(
|
||||
|
@ -1584,8 +1584,10 @@ class PowerMaxMasking(object):
|
||||
self.add_volume_to_default_storage_group(
|
||||
serial_number, device_id, volume_name,
|
||||
extra_specs, src_sg=storagegroup_name)
|
||||
# Delete the storage group.
|
||||
self.rest.delete_storage_group(serial_number, storagegroup_name)
|
||||
# Remove last volume and delete the storage group.
|
||||
self._remove_last_vol_and_delete_sg(
|
||||
serial_number, device_id, volume_name,
|
||||
storagegroup_name, extra_specs)
|
||||
status = True
|
||||
else:
|
||||
num_vols_parent = self.rest.get_num_vols_in_sg(
|
||||
@ -1716,11 +1718,21 @@ class PowerMaxMasking(object):
|
||||
serial_number, device_id, "",
|
||||
extra_specs, src_sg=child_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)
|
||||
LOG.debug("Storage Group %(storagegroup_name)s "
|
||||
"successfully deleted.",
|
||||
{'storagegroup_name': parent_sg_name})
|
||||
self.rest.delete_storage_group(serial_number, child_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)
|
||||
|
||||
LOG.debug("Storage Group %(storagegroup_name)s successfully deleted.",
|
||||
{'storagegroup_name': child_sg_name})
|
||||
|
@ -133,7 +133,8 @@ class PowerMaxVolumeMetadata(object):
|
||||
self.version_dict['serial_number'] = serial_number
|
||||
array_info_dict = self.rest.get_array_detail(serial_number)
|
||||
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['powermax_cinder_driver_version'] = (
|
||||
self.powermax_driver_version)
|
||||
|
@ -476,7 +476,17 @@ class PowerMaxProvision(object):
|
||||
srp_capacity['subscribed_total_tb'] * units.Ki)
|
||||
array_reserve_percent = srp_details['reserved_cap_percent']
|
||||
except KeyError:
|
||||
pass
|
||||
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:
|
||||
pass
|
||||
|
||||
return (total_capacity_gb, remaining_capacity_gb,
|
||||
subscribed_capacity_gb, array_reserve_percent)
|
||||
|
@ -36,8 +36,10 @@ LOG = logging.getLogger(__name__)
|
||||
SLOPROVISIONING = 'sloprovisioning'
|
||||
REPLICATION = 'replication'
|
||||
SYSTEM = 'system'
|
||||
U4V_VERSION = '92'
|
||||
MIN_U4P_VERSION = '9.2.0.0'
|
||||
U4P_100_VERSION = '100'
|
||||
MIN_U4P_100_VERSION = '10.0.0.0'
|
||||
U4P_92_VERSION = '92'
|
||||
MIN_U4P_92_VERSION = '9.2.0.0'
|
||||
UCODE_5978 = '5978'
|
||||
retry_exc_tuple = (exception.VolumeBackendAPIException,)
|
||||
u4p_failover_max_wait = 120
|
||||
@ -60,6 +62,11 @@ SUCCEEDED = 'succeeded'
|
||||
CREATE_VOL_STRING = "Creating new 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):
|
||||
"""Rest class based on Unisphere for PowerMax Rest API."""
|
||||
@ -85,6 +92,7 @@ class PowerMaxRest(object):
|
||||
self.ucode_major_level = None
|
||||
self.ucode_minor_level = None
|
||||
self.is_snap_id = False
|
||||
self.u4p_version = None
|
||||
|
||||
def set_rest_credentials(self, array_info):
|
||||
"""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" % {
|
||||
'ip_port': ip_port})
|
||||
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.get_major_minor_ucode(array_info['SerialNumber']))
|
||||
self.get_major_minor_ucode(serial_number))
|
||||
self.is_snap_id = self._is_snapid_enabled()
|
||||
|
||||
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
|
||||
"""
|
||||
@ -372,7 +386,7 @@ class PowerMaxRest(object):
|
||||
rc -- int, status -- string, task -- list of dicts
|
||||
"""
|
||||
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')
|
||||
if job:
|
||||
status = job['status']
|
||||
@ -448,8 +462,7 @@ class PowerMaxRest(object):
|
||||
|
||||
return target_uri
|
||||
|
||||
@staticmethod
|
||||
def _build_uri_legacy_args(*args, **kwargs):
|
||||
def _build_uri_legacy_args(self, *args, **kwargs):
|
||||
"""Build the target URI using legacy args & kwargs.
|
||||
|
||||
Expected format:
|
||||
@ -458,7 +471,7 @@ class PowerMaxRest(object):
|
||||
arg[2]: the resource type e.g. 'maskingview' -- str
|
||||
kwarg resource_name: the name of a specific resource -- str
|
||||
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
|
||||
|
||||
:param args: input args -- see above
|
||||
@ -470,7 +483,7 @@ class PowerMaxRest(object):
|
||||
# Extract keyword args following legacy _build_uri() format
|
||||
resource_name = kwargs.get('resource_name')
|
||||
private = kwargs.get('private')
|
||||
version = kwargs.get('version', U4V_VERSION)
|
||||
version = kwargs.get('version', self.u4p_version)
|
||||
if kwargs.get('no_version'):
|
||||
version = None
|
||||
|
||||
@ -489,8 +502,7 @@ class PowerMaxRest(object):
|
||||
|
||||
return target_uri
|
||||
|
||||
@staticmethod
|
||||
def _build_uri_kwargs(**kwargs):
|
||||
def _build_uri_kwargs(self, **kwargs):
|
||||
"""Build the target URI using kwargs.
|
||||
|
||||
Expected kwargs:
|
||||
@ -517,7 +529,7 @@ class PowerMaxRest(object):
|
||||
:param kwargs: input keyword args -- see above
|
||||
:return: target URI -- str
|
||||
"""
|
||||
version = kwargs.get('version', U4V_VERSION)
|
||||
version = kwargs.get('version', self.u4p_version)
|
||||
if kwargs.get('no_version'):
|
||||
version = None
|
||||
|
||||
@ -607,7 +619,7 @@ class PowerMaxRest(object):
|
||||
|
||||
def get_resource(self, array, category, resource_type,
|
||||
resource_name=None, params=None, private=False,
|
||||
version=U4V_VERSION):
|
||||
version=None):
|
||||
"""Get resource details from array.
|
||||
|
||||
:param array: the array serial number
|
||||
@ -621,7 +633,7 @@ class PowerMaxRest(object):
|
||||
"""
|
||||
target_uri = self.build_uri(
|
||||
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)
|
||||
|
||||
def create_resource(self, array, category, resource_type, payload,
|
||||
@ -645,7 +657,7 @@ class PowerMaxRest(object):
|
||||
return status_code, message
|
||||
|
||||
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):
|
||||
"""Modify a resource.
|
||||
|
||||
@ -660,7 +672,7 @@ class PowerMaxRest(object):
|
||||
"""
|
||||
target_uri = self.build_uri(
|
||||
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,
|
||||
request_object=payload)
|
||||
operation = 'modify %(res)s resource' % {'res': resource_type}
|
||||
@ -695,7 +707,7 @@ class PowerMaxRest(object):
|
||||
|
||||
: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')
|
||||
if not array_details:
|
||||
LOG.error("Could not get array details from Unisphere instance.")
|
||||
@ -708,7 +720,7 @@ class PowerMaxRest(object):
|
||||
:param array: the array serial number
|
||||
: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')
|
||||
if not array_details:
|
||||
LOG.error("Cannot connect to array %(array)s.",
|
||||
@ -721,7 +733,8 @@ class PowerMaxRest(object):
|
||||
:param array: the array serial number
|
||||
: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')
|
||||
return array_tags.get('tag_name')
|
||||
|
||||
@ -734,7 +747,8 @@ class PowerMaxRest(object):
|
||||
is_next_gen = False
|
||||
array_details = self.get_array_detail(array)
|
||||
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:
|
||||
is_next_gen = True
|
||||
return is_next_gen
|
||||
@ -749,7 +763,7 @@ class PowerMaxRest(object):
|
||||
if response and response.get('version'):
|
||||
version = response['version']
|
||||
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
|
||||
|
||||
def get_unisphere_version(self):
|
||||
@ -757,12 +771,8 @@ class PowerMaxRest(object):
|
||||
|
||||
:returns: version dict
|
||||
"""
|
||||
post_90_endpoint = '/version'
|
||||
pre_91_endpoint = '/system/version'
|
||||
|
||||
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)
|
||||
version_endpoint = '/version'
|
||||
status_code, version_dict = self.request(version_endpoint, GET)
|
||||
|
||||
if not version_dict:
|
||||
LOG.error("Unisphere version info not found.")
|
||||
@ -844,7 +854,8 @@ class PowerMaxRest(object):
|
||||
if system_info and system_info.get('model'):
|
||||
array_model = system_info.get('model')
|
||||
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:
|
||||
is_next_gen = True
|
||||
return array_model, is_next_gen
|
||||
@ -858,7 +869,8 @@ class PowerMaxRest(object):
|
||||
ucode_version = None
|
||||
system_info = self.get_array_detail(array)
|
||||
if system_info:
|
||||
ucode_version = system_info['ucode']
|
||||
ucode_version = system_info.get(
|
||||
'ucode', system_info.get('microcode'))
|
||||
return ucode_version
|
||||
|
||||
def is_compression_capable(self, array):
|
||||
@ -869,7 +881,7 @@ class PowerMaxRest(object):
|
||||
"""
|
||||
is_compression_capable = False
|
||||
target_uri = ("/%s/sloprovisioning/symmetrix?compressionCapable=true"
|
||||
% U4V_VERSION)
|
||||
% self.u4p_version)
|
||||
status_code, message = self.request(target_uri, GET)
|
||||
self.check_status_code_success(
|
||||
"Check if compression enabled", status_code, message)
|
||||
@ -1018,7 +1030,7 @@ class PowerMaxRest(object):
|
||||
return storagegroup_name
|
||||
|
||||
def modify_storage_group(self, array, storagegroup, payload,
|
||||
version=U4V_VERSION):
|
||||
version=None):
|
||||
"""Modify a storage group (PUT operation).
|
||||
|
||||
:param version: the uv4 version
|
||||
@ -1028,8 +1040,8 @@ class PowerMaxRest(object):
|
||||
:returns: status_code -- int, message -- string, server response
|
||||
"""
|
||||
return self.modify_resource(
|
||||
array, SLOPROVISIONING, 'storagegroup', payload, version,
|
||||
resource_name=storagegroup)
|
||||
array, SLOPROVISIONING, 'storagegroup', payload,
|
||||
self.u4p_version, resource_name=storagegroup)
|
||||
|
||||
def modify_storage_array(self, array, payload):
|
||||
"""Modify a storage array (PUT operation).
|
||||
@ -1038,7 +1050,8 @@ class PowerMaxRest(object):
|
||||
:param payload: the request payload
|
||||
: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,
|
||||
request_object=payload)
|
||||
operation = 'modify %(res)s resource' % {'res': 'symmetrix'}
|
||||
@ -1417,13 +1430,8 @@ class PowerMaxRest(object):
|
||||
:returns: volume dict
|
||||
: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(
|
||||
array, SLOPROVISIONING, 'volume', resource_name=device_id,
|
||||
version=version)
|
||||
array, SLOPROVISIONING, 'volume', resource_name=device_id)
|
||||
if not volume_dict:
|
||||
exception_message = (_("Volume %(deviceID)s not found.")
|
||||
% {'deviceID': device_id})
|
||||
@ -1570,7 +1578,8 @@ class PowerMaxRest(object):
|
||||
raise exception.VolumeBackendAPIException(exception_message)
|
||||
|
||||
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
|
||||
try:
|
||||
self.delete_resource(array, SLOPROVISIONING,
|
||||
@ -1807,13 +1816,23 @@ class PowerMaxRest(object):
|
||||
:param ip_address: the ip address associated with the 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]
|
||||
params = {'ip_list': ip_address, 'iscsi_target': False}
|
||||
params = {'ip_list': ip_address, target_key: False}
|
||||
target_uri = self.build_uri(
|
||||
category=SYSTEM, resource_level='symmetrix',
|
||||
resource_level_id=array_id, resource_type='director',
|
||||
resource_type_id=director_id, resource='port')
|
||||
|
||||
port_info = self.get_request(
|
||||
target_uri, 'port IP interface', params)
|
||||
if not port_info:
|
||||
@ -2110,7 +2129,7 @@ class PowerMaxRest(object):
|
||||
"""
|
||||
array_capabilities = None
|
||||
target_uri = ("/%s/replication/capabilities/symmetrix"
|
||||
% U4V_VERSION)
|
||||
% self.u4p_version)
|
||||
capabilities = self.get_request(
|
||||
target_uri, 'replication capabilities')
|
||||
if capabilities:
|
||||
@ -3449,19 +3468,25 @@ class PowerMaxRest(object):
|
||||
:returns: unisphere_meets_min_req -- boolean
|
||||
"""
|
||||
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
|
||||
|
||||
if running_version and (running_version[0].isalpha()):
|
||||
# remove leading letter
|
||||
if running_version.lower()[0] == 'v':
|
||||
if running_version.lower()[0] == QUAL_CODE:
|
||||
version = running_version[1:]
|
||||
unisphere_meets_min_req = (
|
||||
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 "
|
||||
"Unisphere.", {'version': running_version})
|
||||
return major_version >= U4V_VERSION
|
||||
return int(major_version) >= int(self.u4p_version)
|
||||
|
||||
if unisphere_meets_min_req:
|
||||
LOG.info("Unisphere version %(running_version)s meets minimum "
|
||||
@ -3522,7 +3547,8 @@ class PowerMaxRest(object):
|
||||
ucode_minor_level = 0
|
||||
|
||||
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_major_level = ucode_level[0]
|
||||
ucode_minor_level = ucode_level[1]
|
||||
@ -3534,21 +3560,5 @@ class PowerMaxRest(object):
|
||||
:returns: boolean
|
||||
"""
|
||||
return (self.ucode_major_level >= utils.UCODE_5978 and
|
||||
self.ucode_minor_level >= utils.UCODE_5978_HICKORY)
|
||||
|
||||
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)
|
||||
self.ucode_minor_level >= utils.UCODE_5978_HICKORY) or (
|
||||
self.ucode_major_level >= utils.UCODE_6079)
|
||||
|
@ -45,6 +45,7 @@ TRUNCATE_27 = 27
|
||||
UCODE_5978_ELMSR = 221
|
||||
UCODE_5978_HICKORY = 660
|
||||
UCODE_5978 = 5978
|
||||
UCODE_6079 = 6079
|
||||
UPPER_HOST_CHARS = 16
|
||||
UPPER_PORT_GROUP_CHARS = 12
|
||||
|
||||
@ -140,7 +141,8 @@ PORT_GROUP_LABEL = 'port_group_label'
|
||||
# Array Models, Service Levels & Workloads
|
||||
VMAX_HYBRID_MODELS = ['VMAX100K', 'VMAX200K', 'VMAX400K']
|
||||
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',
|
||||
'None', 'NONE']
|
||||
@ -2124,3 +2126,14 @@ class PowerMaxUtils(object):
|
||||
:returns: str
|
||||
"""
|
||||
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))
|
||||
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Dell PowerMax driver now supports Unisphere for PowerMax 10.0
|
Loading…
Reference in New Issue
Block a user