Delete iSCSI devices after volume detached

Previously, after detaching from a volume, the iSCSI device
remains attached on the compute node until all LUNs for a given
IQN are detached -- causing issues when LUNs are reused for
different volumes. This change will delete the device(s)
associated with the detached volume so LUNs can be reused.

Fixes bug 1112483
Change-Id: Icae3ec4d1ee2036fbba7b9eb5c03a1c86014fcc0
This commit is contained in:
Jason Dillaman 2013-07-17 16:17:08 -04:00
parent 62e549eb02
commit 8a6ee48087
3 changed files with 89 additions and 8 deletions

View File

@ -209,3 +209,6 @@ rpc.mountd: CommandFilter, rpc.mountd, root
# nova/virt/libvirt/utils.py:
rbd: CommandFilter, rbd, root
# nova/virt/libvirt/volume.py: 'cp', '/dev/stdin', delete_control..
cp: CommandFilter, cp, root

View File

@ -260,6 +260,8 @@ class LibvirtVolumeTestCase(test.NoDBTestCase):
('iscsiadm', '-m', 'node', '-T', self.iqn,
'-p', self.location, '--op', 'update',
'-n', 'node.startup', '-v', 'automatic'),
('iscsiadm', '-m', 'node', '-T', self.iqn,
'-p', self.location, '--rescan'),
('iscsiadm', '-m', 'node', '-T', self.iqn,
'-p', self.location, '--op', 'update',
'-n', 'node.startup', '-v', 'manual'),
@ -274,15 +276,16 @@ class LibvirtVolumeTestCase(test.NoDBTestCase):
# NOTE(vish) exists is to make driver assume connecting worked
self.stubs.Set(os.path, 'exists', lambda x: True)
libvirt_driver = volume.LibvirtISCSIVolumeDriver(self.fake_conn)
devs = ['/dev/disk/by-path/ip-%s-iscsi-%s-lun-1' % (self.location,
name = 'volume-00000001'
devs = ['/dev/disk/by-path/ip-%s-iscsi-%s-lun-2' % (self.location,
self.iqn)]
self.stubs.Set(self.fake_conn, 'get_all_block_devices', lambda: devs)
vol = {'id': 1, 'name': self.name}
connection_info = self.iscsi_connection(vol, self.location, self.iqn)
conf = libvirt_driver.connect_volume(connection_info, self.disk_info)
tree = conf.format_dom()
dev_str = '/dev/disk/by-path/ip-%s-iscsi-%s-lun-1' % (self.location,
self.iqn)
dev_name = 'ip-%s-iscsi-%s-lun-1' % (self.location, self.iqn)
dev_str = '/dev/disk/by-path/%s' % dev_name
self.assertEqual(tree.get('type'), 'block')
self.assertEqual(tree.find('./source').get('dev'), dev_str)
libvirt_driver.disconnect_volume(connection_info, "vde")
@ -293,7 +296,11 @@ class LibvirtVolumeTestCase(test.NoDBTestCase):
'-p', self.location, '--login'),
('iscsiadm', '-m', 'node', '-T', self.iqn,
'-p', self.location, '--op', 'update',
'-n', 'node.startup', '-v', 'automatic')]
'-n', 'node.startup', '-v', 'automatic'),
('iscsiadm', '-m', 'node', '-T', self.iqn,
'-p', self.location, '--rescan'),
('cp', '/dev/stdin',
'/sys/block/%s/device/delete' % dev_name)]
self.assertEqual(self.executes, expected_commands)
def iser_connection(self, volume, location, iqn):
@ -481,6 +488,51 @@ class LibvirtVolumeTestCase(test.NoDBTestCase):
expected_multipath_cmd = ('multipath', '-f', 'foo')
self.assertIn(expected_multipath_cmd, self.executes)
def test_libvirt_kvm_volume_with_multipath_still_in_use(self):
name = 'volume-00000001'
location = '10.0.2.15:3260'
iqn = 'iqn.2010-10.org.openstack:%s' % name
mpdev_filepath = '/dev/mapper/foo'
def _get_multipath_device_name(path):
if '%s-lun-1' % iqn in path:
return mpdev_filepath
return '/dev/mapper/donotdisconnect'
self.flags(iscsi_use_multipath=True, group='libvirt')
self.stubs.Set(os.path, 'exists', lambda x: True)
libvirt_driver = volume.LibvirtISCSIVolumeDriver(self.fake_conn)
libvirt_driver._get_multipath_device_name =\
lambda x: _get_multipath_device_name(x)
block_devs = ['/dev/disks/by-path/%s-iscsi-%s-lun-2' % (location, iqn)]
self.stubs.Set(self.fake_conn, 'get_all_block_devices',
lambda: block_devs)
vol = {'id': 1, 'name': name}
connection_info = self.iscsi_connection(vol, location, iqn)
connection_info['data']['device_path'] = mpdev_filepath
libvirt_driver._get_multipath_iqn = lambda x: iqn
iscsi_devs = ['1.2.3.4-iscsi-%s-lun-1' % iqn,
'%s-iscsi-%s-lun-1' % (location, iqn),
'%s-iscsi-%s-lun-2' % (location, iqn)]
libvirt_driver._get_iscsi_devices = lambda: iscsi_devs
# Set up disconnect volume mock expectations
self.mox.StubOutWithMock(libvirt_driver, '_delete_device')
self.mox.StubOutWithMock(libvirt_driver, '_rescan_multipath')
libvirt_driver._rescan_multipath()
libvirt_driver._delete_device('/dev/disk/by-path/%s' % iscsi_devs[0])
libvirt_driver._delete_device('/dev/disk/by-path/%s' % iscsi_devs[1])
libvirt_driver._rescan_multipath()
# Ensure that the mpath devices are deleted
self.mox.ReplayAll()
libvirt_driver.disconnect_volume(connection_info, 'vde')
def test_libvirt_kvm_volume_with_multipath_getmpdev(self):
self.flags(iscsi_use_multipath=True, group='libvirt')
self.stubs.Set(os.path, 'exists', lambda x: True)

View File

@ -290,6 +290,9 @@ class LibvirtISCSIVolumeDriver(LibvirtBaseVolumeDriver):
else:
self._connect_to_iscsi_portal(iscsi_properties)
# Detect new/resized LUNs for existing sessions
self._run_iscsiadm(iscsi_properties, ("--rescan",))
host_device = self._get_host_device(iscsi_properties)
# The /dev/disk/by-path/... node is not always present immediately
@ -336,12 +339,9 @@ class LibvirtISCSIVolumeDriver(LibvirtBaseVolumeDriver):
def disconnect_volume(self, connection_info, disk_dev):
"""Detach the volume from instance_name."""
iscsi_properties = connection_info['data']
host_device = self._get_host_device(iscsi_properties)
multipath_device = None
if self.use_multipath:
host_device = ("/dev/disk/by-path/ip-%s-iscsi-%s-lun-%s" %
(iscsi_properties['target_portal'],
iscsi_properties['target_iqn'],
iscsi_properties.get('target_lun', 0)))
multipath_device = self._get_multipath_device_name(host_device)
super(LibvirtISCSIVolumeDriver,
@ -360,6 +360,19 @@ class LibvirtISCSIVolumeDriver(LibvirtBaseVolumeDriver):
devices = [dev for dev in devices if dev.startswith(device_prefix)]
if not devices:
self._disconnect_from_iscsi_portal(iscsi_properties)
elif host_device not in devices:
# Delete device if LUN is not in use by another instance
self._delete_device(host_device)
def _delete_device(self, device_path):
device_name = os.path.basename(os.path.realpath(device_path))
delete_control = '/sys/block/' + device_name + '/device/delete'
if os.path.exists(delete_control):
# Copy '1' from stdin to the device delete control file
utils.execute('cp', '/dev/stdin', delete_control,
process_input='1', run_as_root=True)
else:
LOG.warn(_("Unable to delete volume device %s"), device_name)
def _remove_multipath_device_descriptor(self, disk_descriptor):
disk_descriptor = disk_descriptor.replace('/dev/mapper/', '')
@ -401,6 +414,9 @@ class LibvirtISCSIVolumeDriver(LibvirtBaseVolumeDriver):
# disconnect if no other multipath devices with same iqn
self._disconnect_mpath(iscsi_properties)
return
elif multipath_device not in devices:
# delete the devices associated w/ the unused multipath
self._delete_mpath(iscsi_properties, multipath_device)
# else do not disconnect iscsi portals,
# as they are used for other luns,
@ -497,6 +513,16 @@ class LibvirtISCSIVolumeDriver(LibvirtBaseVolumeDriver):
return []
return [entry for entry in devices if entry.startswith("ip-")]
def _delete_mpath(self, iscsi_properties, multipath_device):
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)
self._rescan_multipath()
def _disconnect_mpath(self, iscsi_properties):
entries = self._get_iscsi_devices()
ips = [ip.split("-")[1] for ip in entries