diff --git a/cinder/tests/unit/test_huawei_drivers.py b/cinder/tests/unit/test_huawei_drivers.py index 28f823518be..5996819d0d4 100644 --- a/cinder/tests/unit/test_huawei_drivers.py +++ b/cinder/tests/unit/test_huawei_drivers.py @@ -225,6 +225,7 @@ FAKE_CREATE_VOLUME_RESPONSE = {"ID": "1", "WWN": '6643e8c1004c5f6723e9f454003'} FakeConnector = {'initiator': 'iqn.1993-08.debian:01:ec2bff7ac3a3', + 'multipath': False, 'wwpns': ['10000090fa0d6754'], 'wwnns': ['10000090fa0d6755'], 'host': 'ubuntuc', @@ -522,17 +523,18 @@ FAKE_GET_SNAPSHOT_INFO_RESPONSE = """ """ # A fake response of get iscsi response + FAKE_GET_ISCSI_INFO_RESPONSE = """ { "data": [{ "ETHPORTID": "139267", - "ID": "iqn.oceanstor:21004846fb8ca15f::22003:192.0.2.244", - "TPGT": "8196", + "ID": "0+iqn.oceanstor:21004846fb8ca15f::22004:192.0.2.1,t,0x2005", + "TPGT": "8197", "TYPE": 249 }, { "ETHPORTID": "139268", - "ID": "iqn.oceanstor:21004846fb8ca15f::22003:192.0.2.244", + "ID": "1+iqn.oceanstor:21004846fb8ca15f::22003:192.0.2.2,t,0x2004", "TPGT": "8196", "TYPE": 249 } @@ -2126,10 +2128,134 @@ class HuaweiISCSIDriverTestCase(test.TestCase): self.assertEqual(driver_data, model_update['replication_driver_data']) self.assertEqual('available', model_update['replication_status']) - def test_initialize_connection_success(self): + def test_initialize_connection_success_multipath_portgroup(self): + temp_connector = copy.deepcopy(FakeConnector) + temp_connector['multipath'] = True + self.mock_object(rest_client.RestClient, 'get_tgt_port_group', + mock.Mock(return_value = '11')) iscsi_properties = self.driver.initialize_connection(test_volume, - FakeConnector) - self.assertEqual(1, iscsi_properties['data']['target_lun']) + temp_connector) + self.assertEqual([1, 1], iscsi_properties['data']['target_luns']) + + def test_initialize_connection_fail_multipath_portgroup(self): + temp_connector = copy.deepcopy(FakeConnector) + temp_connector['multipath'] = True + self.mock_object(rest_client.RestClient, 'get_tgt_port_group', + mock.Mock(return_value = '12')) + self.mock_object(rest_client.RestClient, '_get_tgt_ip_from_portgroup', + mock.Mock(return_value = [])) + self.assertRaises(exception.VolumeBackendAPIException, + self.driver.initialize_connection, + test_volume, temp_connector) + + def test_initialize_connection_success_multipath_targetip(self): + iscsi_info = [{'Name': 'iqn.1993-08.debian:01:ec2bff7ac3a3', + 'TargetIP': '192.0.2.2', + 'CHAPinfo': 'mm-user;mm-user@storage', + 'ALUA': '1'}] + + configuration = mock.Mock(spec = conf.Configuration) + configuration.hypermetro_devices = hypermetro_devices + self.mock_object(time, 'sleep', Fake_sleep) + driver = FakeISCSIStorage(configuration = self.configuration) + driver.do_setup() + driver.configuration.iscsi_info = iscsi_info + driver.client.iscsi_info = iscsi_info + temp_connector = copy.deepcopy(FakeConnector) + temp_connector['multipath'] = True + iscsi_properties = driver.initialize_connection(test_volume, + temp_connector) + self.assertEqual([1], iscsi_properties['data']['target_luns']) + + def test_initialize_connection_fail_multipath_targetip(self): + iscsi_info = [{'Name': 'iqn.1993-08.debian:01:ec2bff7ac3a3', + 'TargetIP': '192.0.2.6', + 'CHAPinfo': 'mm-user;mm-user@storage', + 'ALUA': '1'}] + + configuration = mock.Mock(spec = conf.Configuration) + configuration.hypermetro_devices = hypermetro_devices + self.mock_object(time, 'sleep', Fake_sleep) + driver = FakeISCSIStorage(configuration = self.configuration) + driver.do_setup() + driver.configuration.iscsi_info = iscsi_info + driver.client.iscsi_info = iscsi_info + temp_connector = copy.deepcopy(FakeConnector) + temp_connector['multipath'] = True + self.assertRaises(exception.VolumeBackendAPIException, + driver.initialize_connection, + test_volume, temp_connector) + + def test_initialize_connection_success_multipath_defaultip(self): + iscsi_info = [{'Name': 'iqn.1993-08.debian:01:ec2bff7ac3a3', + 'CHAPinfo': 'mm-user;mm-user@storage', + 'ALUA': '1'}] + default_target_ip = ['192.0.2.2'] + configuration = mock.Mock(spec = conf.Configuration) + configuration.hypermetro_devices = hypermetro_devices + self.mock_object(time, 'sleep', Fake_sleep) + driver = FakeISCSIStorage(configuration = self.configuration) + driver.do_setup() + driver.configuration.iscsi_info = iscsi_info + driver.client.iscsi_info = iscsi_info + driver.configuration.iscsi_default_target_ip = default_target_ip + driver.client.iscsi_default_target_ip = default_target_ip + temp_connector = copy.deepcopy(FakeConnector) + temp_connector['multipath'] = True + iscsi_properties = driver.initialize_connection(test_volume, + temp_connector) + self.assertEqual([1], iscsi_properties['data']['target_luns']) + + def test_initialize_connection_fail_multipath_defaultip(self): + iscsi_info = [{'Name': 'iqn.1993-08.debian:01:ec2bff7ac3a3', + 'CHAPinfo': 'mm-user;mm-user@storage', + 'ALUA': '1'}] + + default_target_ip = ['192.0.2.6'] + configuration = mock.Mock(spec = conf.Configuration) + configuration.hypermetro_devices = hypermetro_devices + self.mock_object(time, 'sleep', Fake_sleep) + driver = FakeISCSIStorage(configuration = self.configuration) + driver.do_setup() + driver.configuration.iscsi_info = iscsi_info + driver.client.iscsi_info = iscsi_info + driver.configuration.iscsi_default_target_ip = default_target_ip + driver.client.iscsi_default_target_ip = default_target_ip + temp_connector = copy.deepcopy(FakeConnector) + temp_connector['multipath'] = True + self.assertRaises(exception.VolumeBackendAPIException, + driver.initialize_connection, + test_volume, temp_connector) + + def test_initialize_connection_fail_no_port_in_portgroup(self): + temp_connector = copy.deepcopy(FakeConnector) + temp_connector['multipath'] = True + self.mock_object(rest_client.RestClient, 'get_tgt_port_group', + mock.Mock(return_value = '11')) + self.mock_object(rest_client.RestClient, '_get_tgt_ip_from_portgroup', + mock.Mock(return_value = [])) + self.assertRaises(exception.VolumeBackendAPIException, + self.driver.initialize_connection, + test_volume, temp_connector) + + def test_initialize_connection_fail_multipath_no_ip(self): + iscsi_info = [{'Name': 'iqn.1993-08.debian:01:ec2bff7ac3a3', + 'CHAPinfo': 'mm-user;mm-user@storage', + 'ALUA': '1'}] + configuration = mock.Mock(spec = conf.Configuration) + configuration.hypermetro_devices = hypermetro_devices + self.mock_object(time, 'sleep', Fake_sleep) + driver = FakeISCSIStorage(configuration = self.configuration) + driver.do_setup() + driver.configuration.iscsi_info = iscsi_info + driver.client.iscsi_info = iscsi_info + driver.configuration.iscsi_default_target_ip = None + driver.client.iscsi_default_target_ip = None + temp_connector = copy.deepcopy(FakeConnector) + temp_connector['multipath'] = True + self.assertRaises(exception.VolumeBackendAPIException, + driver.initialize_connection, + test_volume, temp_connector) def test_terminate_connection_success(self): self.driver.terminate_connection(test_volume, FakeConnector) diff --git a/cinder/volume/drivers/huawei/rest_client.py b/cinder/volume/drivers/huawei/rest_client.py index 6020983dbd3..2c514d91eca 100644 --- a/cinder/volume/drivers/huawei/rest_client.py +++ b/cinder/volume/drivers/huawei/rest_client.py @@ -1213,21 +1213,61 @@ class RestClient(object): def get_iscsi_params(self, connector): """Get target iSCSI params, including iqn, IP.""" initiator = connector['initiator'] + multipath = connector['multipath'] target_ips = [] target_iqns = [] + temp_tgt_ips = [] portgroup = None portgroup_id = None + + if multipath: + for ini in self.iscsi_info: + if ini['Name'] == initiator: + portgroup = ini.get('TargetPortGroup') + if portgroup: + portgroup_id = self.get_tgt_port_group(portgroup) + temp_tgt_ips = self._get_tgt_ip_from_portgroup(portgroup_id) + valid_port_info = self._get_tgt_port_ip_from_rest() + valid_tgt_ips = valid_port_info + + for ip in temp_tgt_ips: + if ip in valid_tgt_ips: + target_ips.append(ip) + + if not target_ips: + msg = (_( + 'get_iscsi_params: No valid port in portgroup. ' + 'portgroup_id: %(id)s, please check it on storage.') + % {'id': portgroup_id}) + LOG.error(msg) + raise exception.VolumeBackendAPIException(data=msg) + + else: + target_ips = self._get_target_ip(initiator) + + else: + target_ips = self._get_target_ip(initiator) + + # Deal with the remote tgt ip. + if 'remote_target_ip' in connector: + target_ips.append(connector['remote_target_ip']) + LOG.info(_LI('Get the default ip: %s.'), target_ips) + + for ip in target_ips: + target_iqn = self._get_tgt_iqn_from_rest(ip) + if not target_iqn: + target_iqn = self._get_tgt_iqn(ip) + if target_iqn: + target_iqns.append(target_iqn) + + return (target_iqns, target_ips, portgroup_id) + + def _get_target_ip(self, initiator): + target_ips = [] for ini in self.iscsi_info: if ini['Name'] == initiator: - for key in ini: - if key == 'TargetPortGroup': - portgroup = ini['TargetPortGroup'] - elif key == 'TargetIP': - target_ips.append(ini['TargetIP']) - - if portgroup: - portgroup_id = self.get_tgt_port_group(portgroup) - target_ips = self._get_tgt_ip_from_portgroup(portgroup_id) + if ini.get('TargetIP'): + target_ips.append(ini.get('TargetIP')) # If not specify target IP for some initiators, use default IP. if not target_ips: @@ -1241,20 +1281,42 @@ class RestClient(object): 'for initiator %(ini)s, please check config file.') % {'ini': initiator}) LOG.error(msg) - raise exception.InvalidInput(reason=msg) + raise exception.VolumeBackendAPIException(data=msg) - # Deal with the remote tgt ip. - if 'remote_target_ip' in connector: - target_ips.append(connector['remote_target_ip']) - LOG.info(_LI('Get the default ip: %s.'), target_ips) - for ip in target_ips: - target_iqn = self._get_tgt_iqn_from_rest(ip) - if not target_iqn: - target_iqn = self._get_tgt_iqn(ip) - if target_iqn: - target_iqns.append(target_iqn) + return target_ips - return (target_iqns, target_ips, portgroup_id) + def _get_tgt_port_ip_from_rest(self): + url = "/iscsi_tgt_port" + result = self.call(url, None, "GET") + info_list = [] + target_ips = [] + if result['error']['code'] != 0: + LOG.warning(_LW("Can't find target port info from rest.")) + return target_ips + + elif not result['data']: + msg = (_( + "Can't find valid IP from rest, please check it on storage.")) + LOG.error(msg) + raise exception.VolumeBackendAPIException(data = msg) + + if 'data' in result: + for item in result['data']: + info_list.append(item['ID']) + + if not info_list: + LOG.warning(_LW("Can't find target port info from rest.")) + return target_ips + + for info in info_list: + split_list = info.split(",") + info_before = split_list[0] + iqn_info = info_before.split("+") + target_iqn = iqn_info[1] + ip_info = target_iqn.split(":") + target_ip = ip_info[-1] + target_ips.append(target_ip) + return target_ips def _get_tgt_iqn_from_rest(self, target_ip): url = "/iscsi_tgt_port" diff --git a/releasenotes/notes/huawei-iscsi-multipath-support-a056201883909287.yaml b/releasenotes/notes/huawei-iscsi-multipath-support-a056201883909287.yaml new file mode 100644 index 00000000000..4754d4baa7c --- /dev/null +++ b/releasenotes/notes/huawei-iscsi-multipath-support-a056201883909287.yaml @@ -0,0 +1,3 @@ +--- +upgrade: + - huawei iscsi multipath support \ No newline at end of file