diff --git a/cinder/tests/unit/volume/drivers/hpe/test_hpe3par.py b/cinder/tests/unit/volume/drivers/hpe/test_hpe3par.py index 018d93694d3..4aaa1f54359 100644 --- a/cinder/tests/unit/volume/drivers/hpe/test_hpe3par.py +++ b/cinder/tests/unit/volume/drivers/hpe/test_hpe3par.py @@ -8996,6 +8996,75 @@ class TestHPE3PARISCSIDriver(HPE3PARBaseDriver): self.assertDictEqual(self.multipath_properties, result) + def test_initialize_connection_multipath_vlan_ip(self): + # setup_mock_client drive with default configuration + # and return the mock HTTP 3PAR client + mock_client = self.setup_driver() + mock_client.getVolume.return_value = {'userCPG': HPE3PAR_CPG} + mock_client.getCPG.return_value = {} + mock_client.getHost.side_effect = [ + hpeexceptions.HTTPNotFound('fake'), + {'name': self.FAKE_HOST}] + mock_client.queryHost.return_value = { + 'members': [{ + 'name': self.FAKE_HOST + }] + } + + mock_client.getHostVLUNs.side_effect = [ + hpeexceptions.HTTPNotFound('fake'), + [{'active': True, + 'volumeName': self.VOLUME_3PAR_NAME, + 'lun': self.TARGET_LUN, 'type': 0, + 'portPos': {'node': 8, 'slot': 1, 'cardPort': 1}}]] + + location = ("%(volume_name)s,%(lun_id)s,%(host)s,%(nsp)s" % + {'volume_name': self.VOLUME_3PAR_NAME, + 'lun_id': self.TARGET_LUN, + 'host': self.FAKE_HOST, + 'nsp': 'something'}) + mock_client.createVLUN.return_value = location + + mock_client.getiSCSIPorts.return_value = [{ + 'IPAddr': '1.1.1.2', + 'iSCSIName': self.TARGET_IQN, + 'iSCSIVlans': [{'IPAddr': '192.168.100.1', + 'iSCSIName': self.TARGET_IQN}] + }] + + with mock.patch.object(hpecommon.HPE3PARCommon, + '_create_client') as mock_create_client: + mock_create_client.return_value = mock_client + volume = copy.deepcopy(self.volume) + volume.replication_status = 'disabled' + result = self.driver.initialize_connection( + volume, + self.connector_multipath_enabled) + + expected = [ + mock.call.getVolume(self.VOLUME_3PAR_NAME), + mock.call.getCPG(HPE3PAR_CPG), + mock.call.getHost(self.FAKE_HOST), + mock.call.queryHost(iqns=['iqn.1993-08.org.debian:01:222']), + mock.call.getHost(self.FAKE_HOST), + mock.call.getiSCSIPorts( + state=self.mock_client_conf['PORT_STATE_READY']), + mock.call.getHostVLUNs(self.FAKE_HOST), + mock.call.createVLUN( + self.VOLUME_3PAR_NAME, + auto=True, + hostname=self.FAKE_HOST, + portPos=self.FAKE_ISCSI_PORT['portPos'], + lun=None), + mock.call.getHostVLUNs(self.FAKE_HOST)] + + mock_client.assert_has_calls( + self.standard_login + + expected + + self.standard_logout) + + self.assertDictEqual(self.multipath_properties, result) + def test_terminate_connection_for_clear_chap_creds_not_found(self): # setup_mock_client drive with default configuration # and return the mock HTTP 3PAR client @@ -10530,6 +10599,24 @@ class TestHPE3PARISCSIDriver(HPE3PARBaseDriver): self.driver.initialize_iscsi_ports, common) + def test_initialize_iscsi_ports_with_vlan_ip(self): + # setup_mock_client drive with default configuration + # and return the mock HTTP 3PAR client + conf = self.setup_configuration() + conf.hpe3par_iscsi_ips = ["192.168.100.1:1234"] + mock_client = self.setup_driver(config=conf) + + mock_client.getPorts.return_value = PORTS_VLAN_RET + expected = [mock.call.getPorts()] + + with mock.patch.object(hpecommon.HPE3PARCommon, + '_create_client') as mock_create_client: + mock_create_client.return_value = mock_client + + common = self.driver._login() + self.driver.initialize_iscsi_ports(common) + mock_client.assert_has_calls(expected) + def test_ensure_export(self): # setup_mock_client drive with default configuration # and return the mock HTTP 3PAR client @@ -10882,6 +10969,19 @@ PORTS_RET = ({'members': 'HWAddr': '2C27D75375D6', 'type': 8}]}) +PORTS_VLAN_RET = ({'members': + [{'portPos': {'node': 1, 'slot': 8, 'cardPort': 2}, + 'protocol': 2, + 'IPAddr': '10.10.220.252', + 'linkState': 4, + 'device': [], + 'iSCSIName': 'iqn.2000-05.com.3pardata:21820002ac00383d', + 'mode': 2, + 'HWAddr': '2C27D75375D2', + 'type': 8, + 'iSCSIVlans': [{'IPAddr': '192.168.100.1'}], + }]}) + VLUNS1_RET = ({'members': [{'portPos': {'node': 1, 'slot': 8, 'cardPort': 2}, 'hostname': 'foo', 'active': True}, diff --git a/cinder/volume/drivers/hpe/hpe_3par_iscsi.py b/cinder/volume/drivers/hpe/hpe_3par_iscsi.py index 2d949840e32..7d38d52b786 100644 --- a/cinder/volume/drivers/hpe/hpe_3par_iscsi.py +++ b/cinder/volume/drivers/hpe/hpe_3par_iscsi.py @@ -129,10 +129,11 @@ class HPE3PARISCSIDriver(hpebasedriver.HPE3PARDriverBase): 4.0.4 - Added Peer Persistence feature 4.0.5 - Added Primera array check. bug #1849525 4.0.6 - Allow iSCSI support for Primera 4.2 onwards + 4.0.7 - Use vlan iscsi ips. Bug #2015034 """ - VERSION = "4.0.6" + VERSION = "4.0.7" # The name of the CI wiki page. CI_WIKI_NAME = "HPE_Storage_CI" @@ -164,6 +165,13 @@ class HPE3PARISCSIDriver(hpebasedriver.HPE3PARDriverBase): finally: self._logout(common) + def _update_dicts(self, temp_iscsi_ip, iscsi_ip_list, ip, port): + ip_port = temp_iscsi_ip[ip]['ip_port'] + iscsi_ip_list[ip] = {'ip_port': ip_port, + 'nsp': port['nsp'], + 'iqn': port['iSCSIName']} + del temp_iscsi_ip[ip] + def initialize_iscsi_ports(self, common, remote_target=None, remote_client=None): # map iscsi_ip-> ip_port @@ -202,15 +210,20 @@ class HPE3PARISCSIDriver(hpebasedriver.HPE3PARDriverBase): # when found, add the valid iSCSI ip, ip port, iqn and nsp # to the iSCSI IP dictionary iscsi_ports = common.get_active_iscsi_target_ports(remote_client) + LOG.debug("iscsi_ports: %(iscsi_ports)s", {'iscsi_ports': iscsi_ports}) for port in iscsi_ports: ip = port['IPAddr'] if ip in temp_iscsi_ip: - ip_port = temp_iscsi_ip[ip]['ip_port'] - iscsi_ip_list[ip] = {'ip_port': ip_port, - 'nsp': port['nsp'], - 'iqn': port['iSCSIName']} - del temp_iscsi_ip[ip] + self._update_dicts(temp_iscsi_ip, iscsi_ip_list, ip, port) + + if 'iSCSIVlans' in port: + for vip in port['iSCSIVlans']: + ip = vip['IPAddr'] + if ip in temp_iscsi_ip: + LOG.debug("vlan ip: %(ip)s", {'ip': ip}) + self._update_dicts(temp_iscsi_ip, iscsi_ip_list, + ip, port) # if the single value iscsi_ip_address option is still in the # temp dictionary it's because it defaults to $my_ip which doesn't @@ -237,6 +250,44 @@ class HPE3PARISCSIDriver(hpebasedriver.HPE3PARDriverBase): self.iscsi_ips[common._client_conf['hpe3par_api_url']] = ( iscsi_ip_list) + def _vlun_create_or_use_existing(self, volume, common, host, iscsi_ips, + target_portals, target_iqns, + target_luns, remote_client, + target_portal_ips, + existing_vluns, iscsi_ip, + lun_id, port): + vlun = None + # check for an already existing VLUN matching the + # nsp for this iSCSI IP. If one is found, use it + # instead of creating a new VLUN. + for v in existing_vluns: + portPos = common.build_portPos( + iscsi_ips[iscsi_ip]['nsp']) + if v['portPos'] == portPos: + vlun = v + break + else: + vlun = common.create_vlun( + volume, host, iscsi_ips[iscsi_ip]['nsp'], + lun_id=lun_id, remote_client=remote_client) + + # This function is called multiple times (from a for loop). + # We want to use the same LUN ID for every port. + # For first port, lun_id is received as None. + # - assign lun_id = vlun['lun'] and return it. + # Thus for subsequent ports, that same lun_id is used + # in create_vlun() above. + if lun_id is None: + lun_id = vlun['lun'] + + iscsi_ip_port = "%s:%s" % ( + iscsi_ip, iscsi_ips[iscsi_ip]['ip_port']) + target_portals.append(iscsi_ip_port) + target_iqns.append(port['iSCSIName']) + target_luns.append(vlun['lun']) + + return lun_id + def _initialize_connection_common(self, volume, connector, common, host, iscsi_ips, ready_ports, target_portals, target_iqns, target_luns, @@ -255,30 +306,30 @@ class HPE3PARISCSIDriver(hpebasedriver.HPE3PARDriverBase): for port in ready_ports: iscsi_ip = port['IPAddr'] if iscsi_ip in target_portal_ips: - vlun = None - # check for an already existing VLUN matching the - # nsp for this iSCSI IP. If one is found, use it - # instead of creating a new VLUN. - for v in existing_vluns: - portPos = common.build_portPos( - iscsi_ips[iscsi_ip]['nsp']) - if v['portPos'] == portPos: - vlun = v - break - else: - vlun = common.create_vlun( - volume, host, iscsi_ips[iscsi_ip]['nsp'], - lun_id=lun_id, remote_client=remote_client) + lun_id = ( + self._vlun_create_or_use_existing( + volume, common, host, iscsi_ips, + target_portals, target_iqns, + target_luns, remote_client, + target_portal_ips, + existing_vluns, iscsi_ip, + lun_id, port)) - # We want to use the same LUN ID for every port - if lun_id is None: - lun_id = vlun['lun'] + if 'iSCSIVlans' in port: + for vip in port['iSCSIVlans']: + iscsi_ip = vip['IPAddr'] + if iscsi_ip in target_portal_ips: + LOG.debug("vlan ip: %(ip)s", {'ip': iscsi_ip}) + + lun_id = ( + self._vlun_create_or_use_existing( + volume, common, host, iscsi_ips, + target_portals, target_iqns, + target_luns, remote_client, + target_portal_ips, + existing_vluns, iscsi_ip, + lun_id, port)) - iscsi_ip_port = "%s:%s" % ( - iscsi_ip, iscsi_ips[iscsi_ip]['ip_port']) - target_portals.append(iscsi_ip_port) - target_iqns.append(port['iSCSIName']) - target_luns.append(vlun['lun']) else: LOG.warning("iSCSI IP: '%s' was not found in " "hpe3par_iscsi_ips list defined in " diff --git a/releasenotes/notes/hpe-3par-use-vlan-iscsi-ips-f75787b0d281030b.yaml b/releasenotes/notes/hpe-3par-use-vlan-iscsi-ips-f75787b0d281030b.yaml new file mode 100644 index 00000000000..42b72c042f4 --- /dev/null +++ b/releasenotes/notes/hpe-3par-use-vlan-iscsi-ips-f75787b0d281030b.yaml @@ -0,0 +1,5 @@ +fixes: + - | + HPE 3PAR driver `Bug #2015034 `_: + Added handling for VLAN iscsi IPs in the 3PAR iSCSI driver. +