diff --git a/cinder/tests/unit/volume/drivers/dell_emc/powermax/powermax_data.py b/cinder/tests/unit/volume/drivers/dell_emc/powermax/powermax_data.py index 3c6a3568cbf..ea9f2314e52 100644 --- a/cinder/tests/unit/volume/drivers/dell_emc/powermax/powermax_data.py +++ b/cinder/tests/unit/volume/drivers/dell_emc/powermax/powermax_data.py @@ -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' diff --git a/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_common.py b/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_common.py index c6d1eccb31e..d8200f8d1a7 100644 --- a/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_common.py +++ b/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_common.py @@ -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}] diff --git a/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_iscsi.py b/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_iscsi.py index 08094281955..b90df8b810c 100644 --- a/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_iscsi.py +++ b/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_iscsi.py @@ -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'] diff --git a/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_masking.py b/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_masking.py index 0f32724fe95..ab83a988340 100644 --- a/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_masking.py +++ b/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_masking.py @@ -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') diff --git a/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_replication.py b/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_replication.py index f883ebff904..d724b9c0ad3 100644 --- a/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_replication.py +++ b/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_replication.py @@ -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' % { diff --git a/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_rest.py b/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_rest.py index bf6ca99b880..8de2dd2be20 100644 --- a/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_rest.py +++ b/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_rest.py @@ -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 = '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 = '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) diff --git a/cinder/volume/drivers/dell_emc/powermax/common.py b/cinder/volume/drivers/dell_emc/powermax/common.py index 0b7146e1bbe..d6583430847 100644 --- a/cinder/volume/drivers/dell_emc/powermax/common.py +++ b/cinder/volume/drivers/dell_emc/powermax/common.py @@ -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( diff --git a/cinder/volume/drivers/dell_emc/powermax/masking.py b/cinder/volume/drivers/dell_emc/powermax/masking.py index 7ad8713a3d4..06e5ec2a84f 100644 --- a/cinder/volume/drivers/dell_emc/powermax/masking.py +++ b/cinder/volume/drivers/dell_emc/powermax/masking.py @@ -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}) diff --git a/cinder/volume/drivers/dell_emc/powermax/metadata.py b/cinder/volume/drivers/dell_emc/powermax/metadata.py index c4f87d87036..cf8e24f2457 100644 --- a/cinder/volume/drivers/dell_emc/powermax/metadata.py +++ b/cinder/volume/drivers/dell_emc/powermax/metadata.py @@ -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) diff --git a/cinder/volume/drivers/dell_emc/powermax/provision.py b/cinder/volume/drivers/dell_emc/powermax/provision.py index 638fc4c1eec..24b7f39baf4 100644 --- a/cinder/volume/drivers/dell_emc/powermax/provision.py +++ b/cinder/volume/drivers/dell_emc/powermax/provision.py @@ -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) diff --git a/cinder/volume/drivers/dell_emc/powermax/rest.py b/cinder/volume/drivers/dell_emc/powermax/rest.py index b2dd4d9474a..43a9dd50e87 100644 --- a/cinder/volume/drivers/dell_emc/powermax/rest.py +++ b/cinder/volume/drivers/dell_emc/powermax/rest.py @@ -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) diff --git a/cinder/volume/drivers/dell_emc/powermax/utils.py b/cinder/volume/drivers/dell_emc/powermax/utils.py index 4c3ce3a16e6..e3139e413da 100644 --- a/cinder/volume/drivers/dell_emc/powermax/utils.py +++ b/cinder/volume/drivers/dell_emc/powermax/utils.py @@ -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)) diff --git a/releasenotes/notes/unisphere-for-powermax-10-support-637dfde0f8fa9862.yaml b/releasenotes/notes/unisphere-for-powermax-10-support-637dfde0f8fa9862.yaml new file mode 100644 index 00000000000..b7e14048285 --- /dev/null +++ b/releasenotes/notes/unisphere-for-powermax-10-support-637dfde0f8fa9862.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Dell PowerMax driver now supports Unisphere for PowerMax 10.0