From 36aeedfd5eeb0345d66fa8456ed6a9447a6514a0 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 Change-Id: I38eafdaee03d136282cfde1fd013e322a4256cc4 --- nova/tests/unit/virt/libvirt/test_volume.py | 73 ++++++++++++++++++++- nova/virt/libvirt/volume.py | 18 ++++- 2 files changed, 88 insertions(+), 3 deletions(-) diff --git a/nova/tests/unit/virt/libvirt/test_volume.py b/nova/tests/unit/virt/libvirt/test_volume.py index cb574954109b..85412db2e905 100644 --- a/nova/tests/unit/virt/libvirt/test_volume.py +++ b/nova/tests/unit/virt/libvirt/test_volume.py @@ -330,6 +330,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', @@ -338,14 +339,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, @@ -602,6 +605,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]]) @@ -703,6 +709,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) @@ -747,6 +813,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 10e635018e6a..c8a57d710727 100644 --- a/nova/virt/libvirt/volume.py +++ b/nova/virt/libvirt/volume.py @@ -442,7 +442,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