From 7307dbaf6b9734ddd456c7b0c1d2c85e053ce61f Mon Sep 17 00:00:00 2001 From: Hiroyuki Eguchi Date: Thu, 4 Dec 2014 15:12:11 +0900 Subject: [PATCH] Fix disconnecting necessary iSCSI sessions issue In Icehouse with "iscsi_use_multipath=true", detaching a multipath iSCSI volume kills all iSCSI volumes visible from the nova compute 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. Closes-Bug: #1382440 (cherry picked from commit 36aeedfd5eeb0345d66fa8456ed6a9447a6514a0) Conflicts: nova/tests/unit/virt/libvirt/test_volume.py Change-Id: I38eafdaee03d136282cfde1fd013e322a4256cc4 --- nova/tests/virt/libvirt/test_volume.py | 73 +++++++++++++++++++++++++- nova/virt/libvirt/volume.py | 18 ++++++- 2 files changed, 88 insertions(+), 3 deletions(-) diff --git a/nova/tests/virt/libvirt/test_volume.py b/nova/tests/virt/libvirt/test_volume.py index dc7511834553..ca362152e7e3 100644 --- a/nova/tests/virt/libvirt/test_volume.py +++ b/nova/tests/virt/libvirt/test_volume.py @@ -341,6 +341,7 @@ Setting up iSCSI targets: unused libvirt_driver = volume.LibvirtISCSIVolumeDriver(self.fake_conn) devs = ['/dev/disk/by-path/ip-%s-iscsi-%s-lun-2' % (self.location, self.iqn)] + iscsi_devs = ['ip-fake-ip-iscsi-fake-portal-lun-2'] with contextlib.nested( mock.patch.object(os.path, 'exists', return_value=True), mock.patch.object(self.fake_conn, '_get_all_block_devices', @@ -349,14 +350,16 @@ Setting up iSCSI targets: unused mock.patch.object(libvirt_driver, '_run_multipath'), mock.patch.object(libvirt_driver, '_get_multipath_device_name', return_value='/dev/mapper/fake-multipath-devname'), + mock.patch.object(libvirt_driver, '_get_iscsi_devices', + return_value=iscsi_devs), mock.patch.object(libvirt_driver, '_get_target_portals_from_iscsiadm_output', return_value=[('fake-ip', 'fake-portal')]), mock.patch.object(libvirt_driver, '_get_multipath_iqn', return_value='fake-portal'), ) as (mock_exists, mock_devices, mock_rescan_multipath, - mock_run_multipath, mock_device_name, mock_get_portals, - mock_get_iqn): + mock_run_multipath, mock_device_name, mock_iscsi_devices, + mock_get_portals, mock_get_iqn): mock_run_multipath.side_effect = processutils.ProcessExecutionError vol = {'id': 1, 'name': self.name} connection_info = self.iscsi_connection(vol, self.location, @@ -619,6 +622,9 @@ Setting up iSCSI targets: unused mpdev_filepath = '/dev/mapper/foo' connection_info['data']['device_path'] = mpdev_filepath libvirt_driver._get_multipath_device_name = lambda x: mpdev_filepath + iscsi_devs = ['ip-%s-iscsi-%s-lun-0' % (self.location, self.iqn)] + self.stubs.Set(libvirt_driver, '_get_iscsi_devices', + lambda: iscsi_devs) self.stubs.Set(libvirt_driver, '_get_target_portals_from_iscsiadm_output', lambda x: [[self.location, self.iqn]]) @@ -679,6 +685,66 @@ Setting up iSCSI targets: unused self.mox.ReplayAll() libvirt_driver.disconnect_volume(connection_info, 'vde') + def test_libvirt_kvm_volume_with_multipath_disconnected(self): + libvirt_driver = volume.LibvirtISCSIVolumeDriver(self.fake_conn) + volumes = [{'name': self.name, + 'location': self.location, + 'iqn': self.iqn, + 'mpdev_filepath': '/dev/mapper/disconnect'}, + {'name': 'volume-00000002', + 'location': '10.0.2.15:3260', + 'iqn': 'iqn.2010-10.org.openstack:volume-00000002', + 'mpdev_filepath': '/dev/mapper/donotdisconnect'}] + iscsi_devs = ['ip-%s-iscsi-%s-lun-1' % (volumes[0]['location'], + volumes[0]['iqn']), + 'ip-%s-iscsi-%s-lun-1' % (volumes[1]['location'], + volumes[1]['iqn'])] + + def _get_multipath_device_name(path): + if '%s-lun-1' % volumes[0]['iqn'] in path: + return volumes[0]['mpdev_filepath'] + else: + return volumes[1]['mpdev_filepath'] + + def _get_multipath_iqn(mpdev): + if volumes[0]['mpdev_filepath'] == mpdev: + return volumes[0]['iqn'] + else: + return volumes[1]['iqn'] + + with contextlib.nested( + mock.patch.object(os.path, 'exists', return_value=True), + mock.patch.object(self.fake_conn, '_get_all_block_devices', + retrun_value=[volumes[1]['mpdev_filepath']]), + mock.patch.object(libvirt_driver, '_get_multipath_device_name', + _get_multipath_device_name), + mock.patch.object(libvirt_driver, '_get_multipath_iqn', + _get_multipath_iqn), + mock.patch.object(libvirt_driver, '_get_iscsi_devices', + return_value=iscsi_devs), + mock.patch.object(libvirt_driver, + '_get_target_portals_from_iscsiadm_output', + return_value=[[volumes[0]['location'], + volumes[0]['iqn']], + [volumes[1]['location'], + volumes[1]['iqn']]]), + mock.patch.object(libvirt_driver, '_disconnect_mpath') + ) as (mock_exists, mock_devices, mock_device_name, mock_get_iqn, + mock_iscsi_devices, mock_get_portals, mock_disconnect_mpath): + vol = {'id': 1, 'name': volumes[0]['name']} + connection_info = self.iscsi_connection(vol, + volumes[0]['location'], + volumes[0]['iqn']) + connection_info['data']['device_path'] =\ + volumes[0]['mpdev_filepath'] + libvirt_driver.use_multipath = True + libvirt_driver.disconnect_volume(connection_info, 'vde') + # Ensure that the mpath device is disconnected. + ips_iqns = [] + ips_iqns.append([volumes[0]['location'], volumes[0]['iqn']]) + mock_disconnect_mpath.assert_called_once_with( + connection_info['data'], ips_iqns) + 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) @@ -722,6 +788,9 @@ Setting up iSCSI targets: unused "type": "disk", } libvirt_driver._get_multipath_device_name = lambda x: mpdev_filepath + iscsi_devs = ['ip-%s-iscsi-%s-lun-0' % (location, iqn)] + self.stubs.Set(libvirt_driver, '_get_iscsi_devices', + lambda: iscsi_devs) self.stubs.Set(libvirt_driver, '_get_target_portals_from_iscsiadm_output', lambda x: [[location, iqn]]) diff --git a/nova/virt/libvirt/volume.py b/nova/virt/libvirt/volume.py index c9046e72b0bb..f377fa31cf7c 100644 --- a/nova/virt/libvirt/volume.py +++ b/nova/virt/libvirt/volume.py @@ -423,7 +423,23 @@ class LibvirtISCSIVolumeDriver(LibvirtBaseVolumeDriver): check_exit_code=[0, 255])[0] \ or "" - ips_iqns = self._get_target_portals_from_iscsiadm_output(out) + # Extract targets for the current multipath device. + ips_iqns = [] + entries = self._get_iscsi_devices() + for ip, iqn in self._get_target_portals_from_iscsiadm_output(out): + ip_iqn = "%s-iscsi-%s" % (ip.split(",")[0], iqn) + for entry in entries: + entry_ip_iqn = entry.split("-lun-")[0] + if entry_ip_iqn[:3] == "ip-": + entry_ip_iqn = entry_ip_iqn[3:] + if (ip_iqn != entry_ip_iqn): + continue + entry_real_path = os.path.realpath("/dev/disk/by-path/%s" % + entry) + entry_mpdev = self._get_multipath_device_name(entry_real_path) + if entry_mpdev == multipath_device: + ips_iqns.append([ip, iqn]) + break if not devices: # disconnect if no other multipath devices