From a80119a5338dd88ca2afde9872cea6f5fad30a67 Mon Sep 17 00:00:00 2001 From: GaoZqiang Date: Fri, 3 Jun 2016 10:58:16 +0800 Subject: [PATCH] huawei-iscsi-multipath-support Adding support for iSCSI multipath in huawei cinder driver. If enable iSCSI multipath, when attaching volume, cinder driver will get all useable eth ports from target storage, and return them to nova. The params like DefaultTargetIP and TargetPortGroup in configure file will not take effect. If iSCSI multipath is not enabled, cinder driver will use the info like DefaultTargetIP or TargetPortGroup in config file to get corresponding ports from target storage. DocImpact Implements: blueprint huawei-iscsi-multipath-support Change-Id: I9d94c2b56073c007a7b71b46ed163326c748f0b4 --- cinder/tests/unit/test_huawei_drivers.py | 138 +++++++++++++++++- cinder/volume/drivers/huawei/rest_client.py | 104 ++++++++++--- ...si-multipath-support-a056201883909287.yaml | 3 + 3 files changed, 218 insertions(+), 27 deletions(-) create mode 100644 releasenotes/notes/huawei-iscsi-multipath-support-a056201883909287.yaml 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