diff --git a/cinder/tests/unit/test_huawei_drivers.py b/cinder/tests/unit/test_huawei_drivers.py index 6c5e40a1ac7..8991f77b856 100644 --- a/cinder/tests/unit/test_huawei_drivers.py +++ b/cinder/tests/unit/test_huawei_drivers.py @@ -737,6 +737,14 @@ MAP_COMMAND_TO_FAKE_RESPONSE['iscsi_initiator?range=[0-256]/GET'] = ( MAP_COMMAND_TO_FAKE_RESPONSE['iscsi_initiator/'] = ( FAKE_ISCSI_INITIATOR_RESPONSE) +MAP_COMMAND_TO_FAKE_RESPONSE['iscsi_initiator/POST'] = ( + FAKE_ISCSI_INITIATOR_RESPONSE) + +MAP_COMMAND_TO_FAKE_RESPONSE['iscsi_initiator/PUT'] = ( + FAKE_ISCSI_INITIATOR_RESPONSE) +MAP_COMMAND_TO_FAKE_RESPONSE['iscsi_initiator/' + 'iqn.1993-08.debian:01:ec2bff7ac3a3/PUT'] = ( + FAKE_ISCSI_INITIATOR_RESPONSE) # mock host info map MAP_COMMAND_TO_FAKE_RESPONSE['host?range=[0-65535]/GET'] = ( FAKE_GET_ALL_HOST_INFO_RESPONSE) @@ -805,6 +813,7 @@ class Fake18000Client(rest_client.RestClient): self.deviceid = None self.test_fail = False self.checkFlag = False + self.remove_chap_flag = False def _change_file_mode(self, filepath): pass @@ -1043,6 +1052,34 @@ class Huawei18000ISCSIDriverTestCase(test.TestCase): self.configuration) self.assertEqual('0', host_os) + def test_find_chap_info(self): + self.driver.restclient.login() + tmp_dict = {} + iscsi_info = {} + tmp_dict['Name'] = 'iqn.1993-08.debian:01:ec2bff7ac3a3' + tmp_dict['CHAPinfo'] = 'mm-user;mm-user@storage' + ini_list = [tmp_dict] + iscsi_info['Initiator'] = ini_list + initiator_name = FakeConnector['initiator'] + chapinfo = self.driver.restclient.find_chap_info(iscsi_info, + initiator_name) + chap_username, chap_password = chapinfo.split(';') + self.assertEqual('mm-user', chap_username) + self.assertEqual('mm-user@storage', chap_password) + + def test_find_alua_info(self): + self.driver.restclient.login() + tmp_dict = {} + iscsi_info = {} + tmp_dict['Name'] = 'iqn.1993-08.debian:01:ec2bff7ac3a3' + tmp_dict['ALUA'] = '1' + ini_list = [tmp_dict] + iscsi_info['Initiator'] = ini_list + initiator_name = FakeConnector['initiator'] + type = self.driver.restclient._find_alua_info(iscsi_info, + initiator_name) + self.assertEqual('1', type) + def create_fake_conf_file(self): """Create a fake Config file. @@ -1115,6 +1152,8 @@ class Huawei18000ISCSIDriverTestCase(test.TestCase): initiator = doc.createElement('Initiator') initiator.setAttribute('Name', 'iqn.1993-08.debian:01:ec2bff7ac3a3') initiator.setAttribute('TargetIP', '192.168.100.2') + initiator.setAttribute('CHAPinfo', 'mm-user;mm-user@storage') + initiator.setAttribute('ALUA', '1') iscsi.appendChild(initiator) host = doc.createElement('Host') diff --git a/cinder/volume/drivers/huawei/huawei_driver.py b/cinder/volume/drivers/huawei/huawei_driver.py index 879892729a2..8b4bbab493f 100644 --- a/cinder/volume/drivers/huawei/huawei_driver.py +++ b/cinder/volume/drivers/huawei/huawei_driver.py @@ -354,7 +354,9 @@ class HuaweiBaseDriver(driver.VolumeDriver): host_name_before_hash) # Add initiator to the host. - self.restclient.ensure_initiator_added(initiator_name, host_id) + self.restclient.ensure_initiator_added(self.xml_file_path, + initiator_name, + host_id) hostgroup_id = self.restclient.add_host_into_hostgroup(host_id) # Mapping lungroup and hostgroup to view. @@ -368,6 +370,9 @@ class HuaweiBaseDriver(driver.VolumeDriver): LOG.info(_LI("initialize_connection_iscsi, host lun id is: %s."), hostlun_id) + iscsi_conf = huawei_utils.get_iscsi_conf(self.xml_file_path) + chapinfo = self.restclient.find_chap_info(iscsi_conf, + initiator_name) # Return iSCSI properties. properties = {} properties['target_discovered'] = False @@ -376,6 +381,13 @@ class HuaweiBaseDriver(driver.VolumeDriver): properties['target_lun'] = int(hostlun_id) properties['volume_id'] = volume['id'] + # If use CHAP, return CHAP info. + if chapinfo: + chap_username, chap_password = chapinfo.split(';') + properties['auth_method'] = 'CHAP' + properties['auth_username'] = chap_username + properties['auth_password'] = chap_password + LOG.info(_LI("initialize_connection_iscsi success. Return data: %s."), properties) return {'driver_volume_type': 'iscsi', 'data': properties} @@ -434,6 +446,8 @@ class HuaweiBaseDriver(driver.VolumeDriver): self.restclient.delete_portgroup_mapping_view(view_id, portgroup_id) if view_id and (int(left_lunnum) <= 0): + self.restclient.remove_chap(initiator_name) + if self.restclient.lungroup_associated(view_id, lungroup_id): self.restclient.delete_lungroup_mapping_view(view_id, lungroup_id) diff --git a/cinder/volume/drivers/huawei/rest_client.py b/cinder/volume/drivers/huawei/rest_client.py index 0501a35c60a..5e1af621447 100644 --- a/cinder/volume/drivers/huawei/rest_client.py +++ b/cinder/volume/drivers/huawei/rest_client.py @@ -388,12 +388,14 @@ class RestClient(object): return lun_id - def ensure_initiator_added(self, initiator_name, host_id): + def ensure_initiator_added(self, xml_file_path, initiator_name, host_id): added = self._initiator_is_added_to_array(initiator_name) if not added: self._add_initiator_to_array(initiator_name) if not self.is_initiator_associated_to_host(initiator_name): - self._associate_initiator_to_host(initiator_name, host_id) + self._associate_initiator_to_host(xml_file_path, + initiator_name, + host_id) def _get_iscsi_tgt_port(self): url = self.url + "/iscsidevicename" @@ -681,17 +683,107 @@ class RestClient(object): result = self.call(url, data) self._assert_rest_result(result, 'Add initiator to array error.') - def _associate_initiator_to_host(self, ininame, host_id): - """Associate initiator with the host.""" - url = self.url + "/iscsi_initiator/" + ininame + def _add_initiator_to_host(self, initiator_name, host_id): + url = self.url + "/iscsi_initiator/" + initiator_name data = json.dumps({"TYPE": "222", - "ID": ininame, + "ID": initiator_name, "USECHAP": "false", "PARENTTYPE": "21", "PARENTID": host_id}) result = self.call(url, data, "PUT") self._assert_rest_result(result, 'Associate initiator to host error.') + def _associate_initiator_to_host(self, + xml_file_path, + initiator_name, + host_id): + """Associate initiator with the host.""" + iscsi_conf = huawei_utils.get_iscsi_conf(xml_file_path) + chapinfo = None + multipathtype = None + + chapinfo = self.find_chap_info(iscsi_conf, + initiator_name) + multipathtype = self._find_alua_info(iscsi_conf, + initiator_name) + if chapinfo: + LOG.info(_LI('Use CHAP when adding initiator to host.')) + self._use_chap(chapinfo, initiator_name, host_id) + else: + self._add_initiator_to_host(initiator_name, host_id) + + if multipathtype: + LOG.info(_LI('Use ALUA when adding initiator to host.')) + self._use_alua(initiator_name, multipathtype) + + def find_chap_info(self, iscsi_conf, initiator_name): + """Find CHAP info from xml.""" + chapinfo = None + for ini in iscsi_conf['Initiator']: + if ini['Name'] == initiator_name: + if 'CHAPinfo' in ini: + chapinfo = ini['CHAPinfo'] + break + + return chapinfo + + def _find_alua_info(self, iscsi_conf, initiator_name): + """Find ALUA info from xml.""" + multipathtype = 0 + for ini in iscsi_conf['Initiator']: + if ini['Name'] == initiator_name: + if 'ALUA' in ini: + if ini['ALUA'] != '1' and ini['ALUA'] != '0': + msg = (_( + 'Invalid ALUA value. ' + 'ALUA value must be 1 or 0.')) + LOG.error(msg) + raise exception.InvalidInput(msg) + else: + multipathtype = ini['ALUA'] + break + return multipathtype + + def _use_chap(self, chapinfo, initiator_name, host_id): + """Use CHAP when adding initiator to host.""" + (chap_username, chap_password) = chapinfo.split(";") + + url = self.url + "/iscsi_initiator/" + initiator_name + data = json.dumps({"TYPE": "222", + "USECHAP": "true", + "CHAPNAME": chap_username, + "CHAPPASSWORD": chap_password, + "ID": initiator_name, + "PARENTTYPE": "21", + "PARENTID": host_id}) + result = self.call(url, data, "PUT") + + self._assert_rest_result(result, + 'Use CHAP to associate initiator ' + 'to host error. Please check the CHAP ' + 'username and password.') + + def _use_alua(self, initiator_name, multipathtype): + """Use ALUA when adding initiator to host.""" + url = self.url + "/iscsi_initiator" + data = json.dumps({"ID": initiator_name, + "MULTIPATHTYPE": multipathtype}) + result = self.call(url, data, "PUT") + + self._assert_rest_result(result, + 'Use ALUA to associate initiator ' + 'to host error.') + + def remove_chap(self, initiator_name): + """Remove CHAP when terminate connection.""" + url = self.url + "/iscsi_initiator" + data = json.dumps({"USECHAP": "false", + "MULTIPATHTYPE": "0", + "ID": initiator_name}) + result = self.call(url, data, "PUT") + + self._assert_rest_result(result, 'Remove CHAP error.') + def find_mapping_view(self, name): """Find mapping view.""" url = self.url + "/mappingview?range=[0-8191]"