Fix connecting unnecessary iSCSI sessions issue

In Icehouse with "iscsi_use_multipath=true", attaching a multipath
iSCSI volume may create unnecessary iSCSI sessions.

The iscsiadm discovery command in connect_volume() returns all of the
targets in the Cinder node, not just the ones related to the multipath
volume which is specified by iqn. If the storage has many targets,
connecting to all these volumes will also result in many unnecessary
connections.

There are two types of iSCSI multipath devices. One which shares
the same iqn between multiple portals, and the other which use
different iqns on different portals. connect_volume() needs to
identify the type by checking iscsiadm the output if the iqn is
used by multiple portals.

This patch changes the behavior of attaching volume:

   1. Identify the type by checking the iscsiadm output.
   2. Connect to the correct targets by connect_to_iscsi_portal().

Closes-Bug: #1382440

(cherry picked from commit fb0de106f2)

Conflicts:
        nova/tests/unit/virt/libvirt/test_volume.py

Change-Id: I488ad0c09bf26a609e27d67b9ef60b65bb45e0ad
This commit is contained in:
Hiroyuki Eguchi 2014-11-20 10:41:36 +09:00
parent 3dcdce39e5
commit cca94d032b
2 changed files with 65 additions and 4 deletions

View File

@ -13,10 +13,12 @@
# License for the specific language governing permissions and limitations
# under the License.
import fixtures
import contextlib
import os
import time
import fixtures
import mock
from oslo.config import cfg
from nova import exception
@ -491,6 +493,46 @@ class LibvirtVolumeTestCase(test.NoDBTestCase):
expected_multipath_cmd = ('multipath', '-f', 'foo')
self.assertIn(expected_multipath_cmd, self.executes)
def test_libvirt_kvm_volume_with_multipath_connecting(self):
libvirt_driver = volume.LibvirtISCSIVolumeDriver(self.fake_conn)
ip_iqns = [[self.location, self.iqn],
['10.0.2.16:3260', self.iqn],
[self.location,
'iqn.2010-10.org.openstack:volume-00000002']]
with contextlib.nested(
mock.patch.object(os.path, 'exists', return_value=True),
mock.patch.object(libvirt_driver, '_run_iscsiadm_bare'),
mock.patch.object(libvirt_driver,
'_get_target_portals_from_iscsiadm_output',
return_value=ip_iqns),
mock.patch.object(libvirt_driver, '_connect_to_iscsi_portal'),
mock.patch.object(libvirt_driver, '_rescan_iscsi'),
mock.patch.object(libvirt_driver, '_get_host_device',
return_value='fake-device'),
mock.patch.object(libvirt_driver, '_rescan_multipath'),
mock.patch.object(libvirt_driver, '_get_multipath_device_name',
return_value='/dev/mapper/fake-mpath-devname')
) as (mock_exists, mock_run_iscsiadm_bare, mock_get_portals,
mock_connect_iscsi, mock_rescan_iscsi, mock_host_device,
mock_rescan_multipath, mock_device_name):
vol = {'id': 1, 'name': self.name}
connection_info = self.iscsi_connection(vol, self.location,
self.iqn)
libvirt_driver.use_multipath = True
libvirt_driver.connect_volume(connection_info, self.disk_info)
# Verify that the supplied iqn is used when it shares the same
# iqn between multiple portals.
connection_info = self.iscsi_connection(vol, self.location,
self.iqn)
props1 = connection_info['data'].copy()
props2 = connection_info['data'].copy()
props2['target_portal'] = '10.0.2.16:3260'
expected_calls = [mock.call(props1), mock.call(props2),
mock.call(props1)]
self.assertEqual(expected_calls, mock_connect_iscsi.call_args_list)
def test_libvirt_kvm_volume_with_multipath_still_in_use(self):
name = 'volume-00000001'
location = '10.0.2.15:3260'

View File

@ -281,10 +281,29 @@ class LibvirtISCSIVolumeDriver(LibvirtBaseVolumeDriver):
check_exit_code=[0, 255])[0] \
or ""
for ip, iqn in self._get_target_portals_from_iscsiadm_output(out):
# There are two types of iSCSI multipath devices. One which shares
# the same iqn between multiple portals, and the other which use
# different iqns on different portals. Try to identify the type by
# checking the iscsiadm output if the iqn is used by multiple
# portals. If it is, it's the former, so use the supplied iqn.
# Otherwise, it's the latter, so try the ip,iqn combinations to
# find the targets which constitutes the multipath device.
ips_iqns = self._get_target_portals_from_iscsiadm_output(out)
same_portal = False
all_portals = set()
match_portals = set()
for ip, iqn in ips_iqns:
all_portals.add(ip)
if iqn == iscsi_properties['target_iqn']:
match_portals.add(ip)
if len(all_portals) == len(match_portals):
same_portal = True
for ip, iqn in ips_iqns:
props = iscsi_properties.copy()
props['target_portal'] = ip
props['target_iqn'] = iqn
props['target_portal'] = ip.split(",")[0]
if not same_portal:
props['target_iqn'] = iqn
self._connect_to_iscsi_portal(props)
self._rescan_iscsi()