Fix disconnecting necessary iSCSI sessions issue

With "iscsi_use_multipath=true", detaching a multipath iSCSI
volume kills all iSCSI volumes visible from the attached node.
When we use different targets(IQNs) associated with same portal
for each different multipath device, all of the targets will
be deleted via disconnect_volume().

This patch fixes the behavior of detaching volume:

  1. Extract the targets for the detached multipath device.
  2. Delete/disconnect the targets for the detached multipath
     device.

(Ported from I38eafdaee03d136282cfde1fd013e322a4256cc4)

Co-Authored-By: Hiroyuki Eguchi <h-eguchi@az.jp.nec.com>
Closes-Bug: #1382440
Change-Id: I5fdbb5765642bac7263ff616ae4b006d9504547a
This commit is contained in:
Tomoki Sekiyama 2015-06-02 18:38:34 -04:00
parent 3bc6fd2bbf
commit 531398504f
2 changed files with 64 additions and 7 deletions

View File

@ -510,7 +510,17 @@ class ISCSIConnector(InitiatorConnector):
# Do a discovery to find all targets.
# Targets for multiple paths for the same multipath device
# may not be the same.
ips_iqns = self._discover_iscsi_portals(connection_properties)
all_ips_iqns = self._discover_iscsi_portals(connection_properties)
# As discovery result may contain other targets' iqns, extract targets
# to be disconnected whose block devices are already deleted here.
ips_iqns = []
entries = map(lambda x: x.lstrip('ip-').split('-lun-')[0],
self._get_iscsi_devices())
for ip, iqn in all_ips_iqns:
ip_iqn = "%s-iscsi-%s" % (ip.split(",")[0], iqn)
if ip_iqn not in entries:
ips_iqns.append([ip, iqn])
if not devices:
# disconnect if no other multipath devices

View File

@ -422,13 +422,15 @@ class ISCSIConnectorTestCase(ConnectorTestCase):
@mock.patch.object(os.path, 'exists', return_value=True)
@mock.patch.object(host_driver.HostDriver, 'get_all_block_devices')
@mock.patch.object(connector.ISCSIConnector, '_get_iscsi_devices')
@mock.patch.object(connector.ISCSIConnector, '_rescan_multipath')
@mock.patch.object(connector.ISCSIConnector, '_run_multipath')
@mock.patch.object(connector.ISCSIConnector, '_get_multipath_device_name')
@mock.patch.object(connector.ISCSIConnector, '_get_multipath_iqn')
def test_connect_volume_with_multiple_portals(
self, mock_get_iqn, mock_device_name, mock_run_multipath,
mock_rescan_multipath, mock_devices, mock_exists):
mock_rescan_multipath, mock_iscsi_devices, mock_devices,
mock_exists):
location1 = '10.0.2.15:3260'
location2 = '10.0.3.15:3260'
name1 = 'volume-00000001-1'
@ -442,6 +444,7 @@ class ISCSIConnectorTestCase(ConnectorTestCase):
devs = ['/dev/disk/by-path/ip-%s-iscsi-%s-lun-1' % (location1, iqn1),
'/dev/disk/by-path/ip-%s-iscsi-%s-lun-2' % (location2, iqn2)]
mock_devices.return_value = devs
mock_iscsi_devices.return_value = devs
mock_device_name.return_value = fake_multipath_dev
mock_get_iqn.return_value = [iqn1, iqn2]
@ -466,6 +469,7 @@ class ISCSIConnectorTestCase(ConnectorTestCase):
@mock.patch.object(os.path, 'exists')
@mock.patch.object(host_driver.HostDriver, 'get_all_block_devices')
@mock.patch.object(connector.ISCSIConnector, '_get_iscsi_devices')
@mock.patch.object(connector.ISCSIConnector, '_rescan_multipath')
@mock.patch.object(connector.ISCSIConnector, '_run_multipath')
@mock.patch.object(connector.ISCSIConnector, '_get_multipath_device_name')
@ -473,8 +477,8 @@ class ISCSIConnectorTestCase(ConnectorTestCase):
@mock.patch.object(connector.ISCSIConnector, '_run_iscsiadm')
def test_connect_volume_with_multiple_portals_primary_error(
self, mock_iscsiadm, mock_get_iqn, mock_device_name,
mock_run_multipath, mock_rescan_multipath, mock_devices,
mock_exists):
mock_run_multipath, mock_rescan_multipath, mock_iscsi_devices,
mock_devices, mock_exists):
location1 = '10.0.2.15:3260'
location2 = '10.0.3.15:3260'
name1 = 'volume-00000001-1'
@ -496,6 +500,7 @@ class ISCSIConnectorTestCase(ConnectorTestCase):
mock_exists.side_effect = lambda x: x != dev1
mock_devices.return_value = [dev2]
mock_iscsi_devices.return_value = [dev2]
mock_device_name.return_value = fake_multipath_dev
mock_get_iqn.return_value = [iqn2]
mock_iscsiadm.side_effect = fake_run_iscsiadm
@ -608,6 +613,7 @@ class ISCSIConnectorTestCase(ConnectorTestCase):
'_get_target_portals_from_iscsiadm_output')
@mock.patch.object(connector.ISCSIConnector, '_rescan_iscsi')
@mock.patch.object(connector.ISCSIConnector, '_rescan_multipath')
@mock.patch.object(connector.ISCSIConnector, '_get_iscsi_devices')
@mock.patch.object(host_driver.HostDriver, 'get_all_block_devices')
@mock.patch.object(connector.ISCSIConnector,
'_disconnect_from_iscsi_portal')
@ -617,7 +623,7 @@ class ISCSIConnectorTestCase(ConnectorTestCase):
@mock.patch.object(os.path, 'exists', return_value=True)
def test_disconnect_volume_multipath_iscsi(
self, exists_mock, multipath_iqn_mock, get_multipath_name_mock,
disconnect_mock, get_all_devices_mock,
disconnect_mock, get_all_devices_mock, get_iscsi_devices_mock,
rescan_multipath_mock, rescan_iscsi_mock, get_portals_mock):
iqn1 = 'iqn.2013-01.ro.com.netapp:node.netapp01'
iqn2 = 'iqn.2013-01.ro.com.netapp:node.netapp02'
@ -628,6 +634,7 @@ class ISCSIConnectorTestCase(ConnectorTestCase):
get_portals_mock.return_value = [[portal, iqn1]]
multipath_iqn_mock.side_effect = iqns
get_all_devices_mock.return_value = [dev, '/dev/mapper/md-1']
get_iscsi_devices_mock.return_value = []
fake_property = {'target_portal': portal,
'target_iqn': iqn1}
self.connector._disconnect_volume_multipath_iscsi(fake_property,
@ -639,6 +646,42 @@ class ISCSIConnectorTestCase(ConnectorTestCase):
'_get_target_portals_from_iscsiadm_output')
@mock.patch.object(connector.ISCSIConnector, '_rescan_iscsi')
@mock.patch.object(connector.ISCSIConnector, '_rescan_multipath')
@mock.patch.object(connector.ISCSIConnector, '_get_iscsi_devices')
@mock.patch.object(host_driver.HostDriver, 'get_all_block_devices')
@mock.patch.object(connector.ISCSIConnector,
'_disconnect_from_iscsi_portal')
@mock.patch.object(connector.ISCSIConnector, '_get_multipath_device_name',
return_value='/dev/mapper/md-3')
@mock.patch.object(connector.ISCSIConnector, '_get_multipath_iqn')
@mock.patch.object(os.path, 'exists', return_value=True)
def test_disconnect_volume_multipath_iscsi_other_targets(
self, exists_mock, multipath_iqn_mock, get_multipath_name_mock,
disconnect_mock, get_all_devices_mock, get_iscsi_devices_mock,
rescan_multipath_mock, rescan_iscsi_mock, get_portals_mock):
iqn1 = 'iqn.2010-10.org.openstack:target-1'
iqn2 = 'iqn.2010-10.org.openstack:target-2'
portal = '10.0.0.1:3260'
dev2 = ('ip-%s-iscsi-%s-lun-0' % (portal, iqn2))
# Multiple targets are discovered, but only block devices for target-1
# is deleted and target-2 is in use.
get_portals_mock.return_value = [[portal, iqn1], [portal, iqn2]]
multipath_iqn_mock.side_effect = [iqn2, iqn2]
get_all_devices_mock.return_value = [dev2, '/dev/mapper/md-1']
get_iscsi_devices_mock.return_value = [dev2]
fake_property = {'target_portal': portal,
'target_iqn': iqn1}
self.connector._disconnect_volume_multipath_iscsi(fake_property,
'fake/multipath')
# Only target-1 should be disconneced.
disconnect_mock.assert_called_once_with(fake_property)
@mock.patch.object(connector.ISCSIConnector,
'_get_target_portals_from_iscsiadm_output')
@mock.patch.object(connector.ISCSIConnector, '_rescan_iscsi')
@mock.patch.object(connector.ISCSIConnector, '_rescan_multipath')
@mock.patch.object(connector.ISCSIConnector, '_get_iscsi_devices',
return_value=[])
@mock.patch.object(host_driver.HostDriver, 'get_all_block_devices',
return_value=[])
@mock.patch.object(connector.ISCSIConnector,
@ -646,7 +689,8 @@ class ISCSIConnectorTestCase(ConnectorTestCase):
@mock.patch.object(os.path, 'exists', return_value=True)
def test_disconnect_volume_multipath_iscsi_without_other_mp_devices(
self, exists_mock, disconnect_mock, get_all_devices_mock,
rescan_multipath_mock, rescan_iscsi_mock, get_portals_mock):
get_iscsi_devices_mock, rescan_multipath_mock, rescan_iscsi_mock,
get_portals_mock):
portal = '10.0.2.15:3260'
name = 'volume-00000001'
iqn = 'iqn.2010-10.org.openstack:%s' % name
@ -663,13 +707,15 @@ class ISCSIConnectorTestCase(ConnectorTestCase):
'_get_target_portals_from_iscsiadm_output')
@mock.patch.object(connector.ISCSIConnector, '_rescan_iscsi')
@mock.patch.object(connector.ISCSIConnector, '_rescan_multipath')
@mock.patch.object(connector.ISCSIConnector, '_get_iscsi_devices')
@mock.patch.object(host_driver.HostDriver, 'get_all_block_devices')
@mock.patch.object(connector.ISCSIConnector,
'_disconnect_from_iscsi_portal')
@mock.patch.object(os.path, 'exists', return_value=False)
def test_disconnect_volume_multipath_iscsi_with_invalid_symlink(
self, exists_mock, disconnect_mock, get_all_devices_mock,
rescan_multipath_mock, rescan_iscsi_mock, get_portals_mock):
get_iscsi_devices_mock, rescan_multipath_mock, rescan_iscsi_mock,
get_portals_mock):
# Simulate a broken symlink by returning False for os.path.exists(dev)
portal = '10.0.0.1:3260'
name = 'volume-00000001'
@ -678,6 +724,7 @@ class ISCSIConnectorTestCase(ConnectorTestCase):
get_portals_mock.return_value = [[portal, iqn]]
get_all_devices_mock.return_value = [dev, '/dev/mapper/md-1']
get_iscsi_devices_mock.return_value = []
fake_property = {'target_portal': portal,
'target_iqn': iqn}