Fixed a problem in iSCSI multipath

Multipathing for attach volume and detach volume operations
doesn't work properly if there are different targets
associated with different portals for a mulitpath device.

Change-Id: Iab316526cf64de60325d90043f73fa3e83fafb4e
Closes-Bug: #1266051
This commit is contained in:
Xing Yang 2014-01-06 17:27:28 -05:00
parent 7646c28315
commit 429ac4dedd
2 changed files with 63 additions and 17 deletions

View File

@ -480,6 +480,9 @@ class LibvirtVolumeTestCase(test.NoDBTestCase):
connection_info['data']['device_path'] = mpdev_filepath
target_portals = ['fake_portal1', 'fake_portal2']
libvirt_driver._get_multipath_device_name = lambda x: mpdev_filepath
self.stubs.Set(libvirt_driver,
'_get_target_portals_from_iscsiadm_output',
lambda x: [[self.location, self.iqn]])
conf = libvirt_driver.connect_volume(connection_info, self.disk_info)
tree = conf.format_dom()
self.assertEqual(tree.find('./source').get('dev'), mpdev_filepath)
@ -521,6 +524,10 @@ class LibvirtVolumeTestCase(test.NoDBTestCase):
'%s-iscsi-%s-lun-2' % (location, iqn)]
libvirt_driver._get_iscsi_devices = lambda: iscsi_devs
self.stubs.Set(libvirt_driver,
'_get_target_portals_from_iscsiadm_output',
lambda x: [[location, iqn]])
# Set up disconnect volume mock expectations
self.mox.StubOutWithMock(libvirt_driver, '_delete_device')
self.mox.StubOutWithMock(libvirt_driver, '_rescan_multipath')
@ -549,6 +556,9 @@ class LibvirtVolumeTestCase(test.NoDBTestCase):
mpdev_filepath = '/dev/mapper/foo'
target_portals = ['fake_portal1', 'fake_portal2']
libvirt_driver._get_multipath_device_name = lambda x: mpdev_filepath
self.stubs.Set(libvirt_driver,
'_get_target_portals_from_iscsiadm_output',
lambda x: [['fake_portal1', 'fake_iqn1']])
conf = libvirt_driver.connect_volume(connection_info, self.disk_info)
tree = conf.format_dom()
self.assertEqual(tree.find('./source').get('dev'), mpdev_filepath)
@ -574,6 +584,9 @@ class LibvirtVolumeTestCase(test.NoDBTestCase):
}
target_portals = ['fake_portal1', 'fake_portal2']
libvirt_driver._get_multipath_device_name = lambda x: mpdev_filepath
self.stubs.Set(libvirt_driver,
'_get_target_portals_from_iscsiadm_output',
lambda x: [[location, iqn]])
conf = libvirt_driver.connect_volume(connection_info, disk_info)
tree = conf.format_dom()
self.assertEqual(tree.find('./source').get('dev'), mpdev_filepath)
@ -608,6 +621,9 @@ class LibvirtVolumeTestCase(test.NoDBTestCase):
}
target_portals = ['fake_portal1', 'fake_portal2']
libvirt_driver._get_multipath_device_name = lambda x: mpdev_filepath
self.stubs.Set(libvirt_driver,
'_get_target_portals_from_iscsiadm_output',
lambda x: [['fake_portal1', 'fake_iqn1']])
conf = libvirt_driver.connect_volume(connection_info, disk_info)
tree = conf.format_dom()
self.assertEqual(tree.find('./source').get('dev'), mpdev_filepath)

View File

@ -256,7 +256,8 @@ class LibvirtISCSIVolumeDriver(LibvirtBaseVolumeDriver):
return self._run_iscsiadm(iscsi_properties, iscsi_command, **kwargs)
def _get_target_portals_from_iscsiadm_output(self, output):
return [line.split()[0] for line in output.splitlines()]
# return both portals and iqns
return [line.split() for line in output.splitlines()]
@utils.synchronized('connect_volume')
def connect_volume(self, connection_info, disk_info):
@ -280,9 +281,10 @@ class LibvirtISCSIVolumeDriver(LibvirtBaseVolumeDriver):
check_exit_code=[0, 255])[0] \
or ""
for ip in self._get_target_portals_from_iscsiadm_output(out):
for ip, iqn in self._get_target_portals_from_iscsiadm_output(out):
props = iscsi_properties.copy()
props['target_portal'] = ip
props['target_iqn'] = iqn
self._connect_to_iscsi_portal(props)
self._rescan_iscsi()
@ -401,21 +403,46 @@ class LibvirtISCSIVolumeDriver(LibvirtBaseVolumeDriver):
if mpdev:
devices.append(mpdev)
# Do a discovery to find all targets.
# Targets for multiple paths for the same multipath device
# may not be the same.
out = self._run_iscsiadm_bare(['-m',
'discovery',
'-t',
'sendtargets',
'-p',
iscsi_properties['target_portal']],
check_exit_code=[0, 255])[0] \
or ""
ips_iqns = self._get_target_portals_from_iscsiadm_output(out)
if not devices:
# disconnect if no other multipath devices
self._disconnect_mpath(iscsi_properties)
self._disconnect_mpath(iscsi_properties, ips_iqns)
return
# Get a target for all other multipath devices
other_iqns = [self._get_multipath_iqn(device)
for device in devices]
# Get all the targets for the current multipath device
current_iqns = [iqn for ip, iqn in ips_iqns]
if iscsi_properties['target_iqn'] not in other_iqns:
in_use = False
for current in current_iqns:
if current in other_iqns:
in_use = True
break
# If no other multipath device attached has the same iqn
# as the current device
if not in_use:
# disconnect if no other multipath devices with same iqn
self._disconnect_mpath(iscsi_properties)
self._disconnect_mpath(iscsi_properties, ips_iqns)
return
elif multipath_device not in devices:
# delete the devices associated w/ the unused multipath
self._delete_mpath(iscsi_properties, multipath_device)
self._delete_mpath(iscsi_properties, multipath_device, ips_iqns)
# else do not disconnect iscsi portals,
# as they are used for other luns,
@ -512,23 +539,26 @@ class LibvirtISCSIVolumeDriver(LibvirtBaseVolumeDriver):
return []
return [entry for entry in devices if entry.startswith("ip-")]
def _delete_mpath(self, iscsi_properties, multipath_device):
def _delete_mpath(self, iscsi_properties, multipath_device, ips_iqns):
entries = self._get_iscsi_devices()
iqn_lun = '%s-lun-%s' % (iscsi_properties['target_iqn'],
iscsi_properties.get('target_lun', 0))
for dev in ['/dev/disk/by-path/%s' % dev for dev in entries
if iqn_lun in dev]:
self._delete_device(dev)
# Loop through ips_iqns to construct all paths
iqn_luns = []
for ip, iqn in ips_iqns:
iqn_lun = '%s-lun-%s' % (iqn,
iscsi_properties.get('target_lun', 0))
iqn_luns.append(iqn_lun)
for dev in ['/dev/disk/by-path/%s' % dev for dev in entries]:
for iqn_lun in iqn_luns:
if iqn_lun in dev:
self._delete_device(dev)
self._rescan_multipath()
def _disconnect_mpath(self, iscsi_properties):
entries = self._get_iscsi_devices()
ips = [ip.split("-")[1] for ip in entries
if iscsi_properties['target_iqn'] in ip]
for ip in ips:
def _disconnect_mpath(self, iscsi_properties, ips_iqns):
for ip, iqn in ips_iqns:
props = iscsi_properties.copy()
props['target_portal'] = ip
props['target_iqn'] = iqn
self._disconnect_from_iscsi_portal(props)
self._rescan_multipath()