diff --git a/cinder/tests/unit/volume/drivers/test_infinidat.py b/cinder/tests/unit/volume/drivers/test_infinidat.py index 792df8ea656..9331b6f5f3f 100644 --- a/cinder/tests/unit/volume/drivers/test_infinidat.py +++ b/cinder/tests/unit/volume/drivers/test_infinidat.py @@ -74,8 +74,10 @@ class InfiniboxDriverTestCaseBase(test.TestCase): # mock external library dependencies infinisdk = self.patch("cinder.volume.drivers.infinidat.infinisdk") capacity = self.patch("cinder.volume.drivers.infinidat.capacity") - self.patch("cinder.volume.drivers.infinidat.iqn") - self.patch("cinder.volume.drivers.infinidat.wwn") + self._iqn = self.patch("cinder.volume.drivers.infinidat.iqn") + self._wwn = self.patch("cinder.volume.drivers.infinidat.wwn") + self._wwn.WWN = mock.Mock + self._iqn.IQN = mock.Mock capacity.byte = 1 capacity.GiB = units.Gi infinisdk.core.exceptions.InfiniSDKException = FakeInfinisdkException @@ -507,6 +509,19 @@ class InfiniboxDriverTestCase(InfiniboxDriverTestCaseBase): self.driver.delete_group_snapshot, None, test_snapgroup, [test_snapshot]) + def test_terminate_connection_force_detach(self): + mock_infinidat_host = mock.Mock() + mock_infinidat_host.get_ports.return_value = [ + self._wwn.WWN(TEST_WWN_1)] + mock_mapping = mock.Mock() + mock_mapping.get_host.return_value = mock_infinidat_host + self._mock_volume.get_logical_units.return_value = [mock_mapping] + # connector is None - force detach - detach all mappings + self.driver.terminate_connection(test_volume, None) + # make sure we actually detached the host mapping + self._mock_host.unmap_volume.assert_called_once() + self._mock_host.safe_delete.assert_called_once() + class InfiniboxDriverTestCaseFC(InfiniboxDriverTestCaseBase): def test_initialize_connection_multiple_wwpns(self): diff --git a/cinder/volume/drivers/infinidat.py b/cinder/volume/drivers/infinidat.py index e55ff00eeb8..46379ba83e8 100644 --- a/cinder/volume/drivers/infinidat.py +++ b/cinder/volume/drivers/infinidat.py @@ -381,6 +381,26 @@ class InfiniboxVolumeDriver(san.SanISCSIDriver): return dict(driver_volume_type='iscsi', data=result_data) + def _get_ports_from_connector(self, infinidat_volume, connector): + if connector is None: + # If no connector was provided it is a force-detach - remove all + # host connections for the volume + if self._protocol == 'FC': + port_cls = wwn.WWN + else: + port_cls = iqn.IQN + ports = [] + for lun_mapping in infinidat_volume.get_logical_units(): + host_ports = lun_mapping.get_host().get_ports() + host_ports = [port for port in host_ports + if isinstance(port, port_cls)] + ports.extend(host_ports) + elif self._protocol == 'FC': + ports = [wwn.WWN(wwpn) for wwpn in connector['wwpns']] + else: + ports = [iqn.IQN(connector['initiator'])] + return ports + @fczm_utils.add_fc_zone @infinisdk_to_cinder_exceptions @coordination.synchronized('infinidat-{self.management_address}-lock') @@ -399,11 +419,10 @@ class InfiniboxVolumeDriver(san.SanISCSIDriver): infinidat_volume = self._get_infinidat_volume(volume) if self._protocol == 'FC': volume_type = 'fibre_channel' - ports = [wwn.WWN(wwpn) for wwpn in connector['wwpns']] else: volume_type = 'iscsi' - ports = [iqn.IQN(connector['initiator'])] result_data = dict() + ports = self._get_ports_from_connector(infinidat_volume, connector) for port in ports: host_name = self._make_host_name(port) host = self._system.hosts.safe_get(name=host_name) @@ -415,16 +434,18 @@ class InfiniboxVolumeDriver(san.SanISCSIDriver): host.unmap_volume(infinidat_volume) except KeyError: continue # volume mapping not found - # check if the host now doesn't have mappings - if host is not None and len(host.get_luns()) == 0: - host.safe_delete() - if self._protocol == 'FC': - # Create initiator-target mapping to delete host entry - target_wwpns = list(self._get_online_fc_ports()) - target_wwpns, target_map = self._build_initiator_target_map( - connector, target_wwpns) - result_data = dict(target_wwn=target_wwpns, - initiator_target_map=target_map) + # check if the host now doesn't have mappings + if host is not None and len(host.get_luns()) == 0: + host.safe_delete() + if self._protocol == 'FC' and connector is not None: + # Create initiator-target mapping to delete host entry + # this is only relevant for regular (specific host) detach + target_wwpns = list(self._get_online_fc_ports()) + target_wwpns, target_map = ( + self._build_initiator_target_map(connector, + target_wwpns)) + result_data = dict(target_wwn=target_wwpns, + initiator_target_map=target_map) return dict(driver_volume_type=volume_type, data=result_data)