libvirt : Add support for --interface option in iscsiadm.
Adds the new libvirt parameter iscsi_transport that can be used to specify an iscsi transport, which used in conjuction with the --interface parameter provides offloaded iscsi support. Also happens to implement code that was originally supposed to be covered by hw-iscsi-device-name-support as this is a requirement for transport support. DocImpact Closes-Bug: #1370226 Implements: blueprint hw-iscsi-device-name-support Implements: blueprint add-open-iscsi-transport-support Change-Id: I1034f1e26e0b00e64430e6347d232793c3401ba8
This commit is contained in:
parent
dc0436194d
commit
554647a4de
|
@ -14,6 +14,7 @@
|
|||
# under the License.
|
||||
|
||||
import contextlib
|
||||
import glob
|
||||
import os
|
||||
import time
|
||||
|
||||
|
@ -253,8 +254,11 @@ class LibvirtVolumeTestCase(test.NoDBTestCase):
|
|||
readonly = tree.find('./readonly')
|
||||
self.assertIsNotNone(readonly)
|
||||
|
||||
def iscsi_connection(self, volume, location, iqn, auth=False):
|
||||
def iscsi_connection(self, volume, location, iqn, auth=False,
|
||||
transport=None):
|
||||
dev_name = 'ip-%s-iscsi-%s-lun-1' % (location, iqn)
|
||||
if transport is not None:
|
||||
dev_name = 'pci-0000:00:00.0-' + dev_name
|
||||
dev_path = '/dev/disk/by-path/%s' % (dev_name)
|
||||
ret = {
|
||||
'driver_volume_type': 'iscsi',
|
||||
|
@ -276,6 +280,15 @@ class LibvirtVolumeTestCase(test.NoDBTestCase):
|
|||
ret['data']['auth_password'] = 'bar'
|
||||
return ret
|
||||
|
||||
def generate_device(self, transport=None, lun=1, short=False):
|
||||
dev_format = "ip-%s-iscsi-%s-lun-%s" % (self.location, self.iqn, lun)
|
||||
if transport:
|
||||
dev_format = "pci-0000:00:00.0-" + dev_format
|
||||
if short:
|
||||
return dev_format
|
||||
fake_dev_path = "/dev/disk/by-path/" + dev_format
|
||||
return fake_dev_path
|
||||
|
||||
def test_rescan_multipath(self):
|
||||
libvirt_driver = volume.LibvirtISCSIVolumeDriver(self.fake_conn)
|
||||
libvirt_driver._rescan_multipath()
|
||||
|
@ -305,12 +318,30 @@ Setting up iSCSI targets: unused
|
|||
out = driver._get_target_portals_from_iscsiadm_output(sample_input)
|
||||
self.assertEqual(out, targets)
|
||||
|
||||
def test_libvirt_iscsi_driver(self):
|
||||
def test_libvirt_iscsi_get_host_device(self, transport=None):
|
||||
libvirt_driver = volume.LibvirtISCSIVolumeDriver(self.fake_conn)
|
||||
connection_info = self.iscsi_connection(self.vol, self.location,
|
||||
self.iqn)
|
||||
iscsi_properties = connection_info['data']
|
||||
expected_device = self.generate_device(transport, 1, False)
|
||||
if transport:
|
||||
self.stubs.Set(glob, 'glob', lambda x: [expected_device])
|
||||
device = libvirt_driver._get_host_device(iscsi_properties)
|
||||
self.assertEqual(expected_device, device)
|
||||
|
||||
def test_libvirt_iscsi_get_host_device_with_transport(self):
|
||||
self.flags(iscsi_transport='fake_transport', group='libvirt')
|
||||
self.test_libvirt_iscsi_get_host_device('fake_transport')
|
||||
|
||||
def test_libvirt_iscsi_driver(self, transport=None):
|
||||
# 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)
|
||||
connection_info = self.iscsi_connection(self.vol, self.location,
|
||||
self.iqn)
|
||||
self.iqn, False, transport)
|
||||
if transport is not None:
|
||||
self.stubs.Set(libvirt_driver, '_get_host_device',
|
||||
lambda x: self.generate_device(transport, 1, False))
|
||||
libvirt_driver.connect_volume(connection_info, self.disk_info)
|
||||
libvirt_driver.disconnect_volume(connection_info, "vde")
|
||||
expected_commands = [('iscsiadm', '-m', 'node', '-T', self.iqn,
|
||||
|
@ -330,19 +361,25 @@ Setting up iSCSI targets: unused
|
|||
'-p', self.location, '--logout'),
|
||||
('iscsiadm', '-m', 'node', '-T', self.iqn,
|
||||
'-p', self.location, '--op', 'delete')]
|
||||
self.assertEqual(self.executes, expected_commands)
|
||||
self.assertEqual(expected_commands, self.executes)
|
||||
|
||||
def test_libvirt_iscsi_driver_still_in_use(self):
|
||||
def test_libvirt_iscsi_driver_with_transport(self):
|
||||
self.flags(iscsi_transport='fake_transport', group='libvirt')
|
||||
self.test_libvirt_iscsi_driver('fake_transport')
|
||||
|
||||
def test_libvirt_iscsi_driver_still_in_use(self, transport=None):
|
||||
# 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-2' % (self.location,
|
||||
self.iqn)]
|
||||
dev_name = self.generate_device(transport, 1, True)
|
||||
if transport is not None:
|
||||
self.stubs.Set(libvirt_driver, '_get_host_device',
|
||||
lambda x: self.generate_device(transport, 1, False))
|
||||
devs = [self.generate_device(transport, 2, False)]
|
||||
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)
|
||||
libvirt_driver.connect_volume(connection_info, self.disk_info)
|
||||
dev_name = 'ip-%s-iscsi-%s-lun-1' % (self.location, self.iqn)
|
||||
libvirt_driver.disconnect_volume(connection_info, "vde")
|
||||
expected_commands = [('iscsiadm', '-m', 'node', '-T', self.iqn,
|
||||
'-p', self.location),
|
||||
|
@ -358,11 +395,19 @@ Setting up iSCSI targets: unused
|
|||
'/sys/block/%s/device/delete' % dev_name)]
|
||||
self.assertEqual(self.executes, expected_commands)
|
||||
|
||||
def test_libvirt_iscsi_driver_disconnect_multipath_error(self):
|
||||
def test_libvirt_iscsi_driver_still_in_use_with_transport(self):
|
||||
self.flags(iscsi_transport='fake_transport', group='libvirt')
|
||||
self.test_libvirt_iscsi_driver_still_in_use('fake_transport')
|
||||
|
||||
def test_libvirt_iscsi_driver_disconnect_multipath_error(self,
|
||||
transport=None):
|
||||
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']
|
||||
if transport is None:
|
||||
prefix = ""
|
||||
else:
|
||||
prefix = "pci-0000:00:00.0-"
|
||||
devs = [self.generate_device(transport, 2, False)]
|
||||
iscsi_devs = ['%sip-fake-ip-iscsi-fake-portal-lun-2' % prefix]
|
||||
with contextlib.nested(
|
||||
mock.patch.object(os.path, 'exists', return_value=True),
|
||||
mock.patch.object(self.fake_conn, '_get_all_block_devices',
|
||||
|
@ -393,13 +438,13 @@ Setting up iSCSI targets: unused
|
|||
['-f', 'fake-multipath-devname'],
|
||||
check_exit_code=[0, 1])
|
||||
|
||||
def test_libvirt_iscsi_driver_get_config(self):
|
||||
def test_libvirt_iscsi_driver_get_config(self, transport=None):
|
||||
libvirt_driver = volume.LibvirtISCSIVolumeDriver(self.fake_conn)
|
||||
dev_name = 'ip-%s-iscsi-%s-lun-1' % (self.location, self.iqn)
|
||||
dev_name = self.generate_device(transport, 1, True)
|
||||
dev_path = '/dev/disk/by-path/%s' % (dev_name)
|
||||
vol = {'id': 1, 'name': self.name}
|
||||
connection_info = self.iscsi_connection(vol, self.location,
|
||||
self.iqn)
|
||||
self.iqn, False, transport)
|
||||
conf = libvirt_driver.get_config(connection_info, self.disk_info)
|
||||
tree = conf.format_dom()
|
||||
self.assertEqual('block', tree.get('type'))
|
||||
|
@ -411,6 +456,10 @@ Setting up iSCSI targets: unused
|
|||
self.assertEqual('block', tree.get('type'))
|
||||
self.assertEqual(dev_path, tree.find('./source').get('dev'))
|
||||
|
||||
def test_libvirt_iscsi_driver_get_config_with_transport(self):
|
||||
self.flags(iscsi_transport = 'fake_transport', group='libvirt')
|
||||
self.test_libvirt_iscsi_driver_get_config('fake_transport')
|
||||
|
||||
def test_libvirt_iscsi_driver_multipath_id(self):
|
||||
libvirt_driver = volume.LibvirtISCSIVolumeDriver(self.fake_conn)
|
||||
libvirt_driver.use_multipath = True
|
||||
|
@ -742,7 +791,9 @@ Setting up iSCSI targets: unused
|
|||
|
||||
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)]
|
||||
'%s-iscsi-%s-lun-2' % (location, iqn),
|
||||
'pci-0000:00:00.0-ip-%s-iscsi-%s-lun-3' % (location,
|
||||
iqn)]
|
||||
libvirt_driver._get_iscsi_devices = lambda: iscsi_devs
|
||||
|
||||
self.stubs.Set(libvirt_driver,
|
||||
|
@ -871,6 +922,9 @@ Setting up iSCSI targets: unused
|
|||
self.stubs.Set(libvirt_driver,
|
||||
'_get_target_portals_from_iscsiadm_output',
|
||||
lambda x: [[location, iqn]])
|
||||
self.stubs.Set(libvirt_driver, '_get_host_device',
|
||||
lambda x: self.generate_device('iser', 0, False))
|
||||
|
||||
libvirt_driver.connect_volume(connection_info, disk_info)
|
||||
conf = libvirt_driver.get_config(connection_info, disk_info)
|
||||
tree = conf.format_dom()
|
||||
|
@ -897,6 +951,8 @@ Setting up iSCSI targets: unused
|
|||
devs = [dev0, dev]
|
||||
self.stubs.Set(self.fake_conn, '_get_all_block_devices', lambda: devs)
|
||||
self.stubs.Set(libvirt_driver, '_get_iscsi_devices', lambda: [])
|
||||
self.stubs.Set(libvirt_driver, '_get_host_device',
|
||||
lambda x: self.generate_device('iser', 1, False))
|
||||
connection_info = self.iser_connection(vol, location, iqn)
|
||||
mpdev_filepath = '/dev/mapper/foo'
|
||||
disk_info = {
|
||||
|
|
|
@ -92,7 +92,16 @@ volume_opts = [
|
|||
cfg.ListOpt('qemu_allowed_storage_drivers',
|
||||
default=[],
|
||||
help='Protocols listed here will be accessed directly '
|
||||
'from QEMU. Currently supported protocols: [gluster]')
|
||||
'from QEMU. Currently supported protocols: [gluster]'),
|
||||
cfg.StrOpt('iscsi_transport',
|
||||
default=None,
|
||||
help='The iSCSI transport to use to connect to target in case '
|
||||
'offload support is desired. Supported transports are '
|
||||
'be2iscsi, bnx2i, cxgb3i, cxgb4i, qla4xx and ocs. '
|
||||
'Default format is transport_name.hwaddress and can be '
|
||||
'generated manually or via iscsiadm -m iface'),
|
||||
# iser is also supported, but use LibvirtISERVolumeDriver
|
||||
# instead
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
@ -283,6 +292,13 @@ class LibvirtISCSIVolumeDriver(LibvirtBaseVolumeDriver):
|
|||
is_block_dev=True)
|
||||
self.num_scan_tries = CONF.libvirt.num_iscsi_scan_tries
|
||||
self.use_multipath = CONF.libvirt.iscsi_use_multipath
|
||||
if CONF.libvirt.iscsi_transport:
|
||||
self.transport = CONF.libvirt.iscsi_transport
|
||||
else:
|
||||
self.transport = 'default'
|
||||
|
||||
def _get_transport(self):
|
||||
return self.transport
|
||||
|
||||
def _run_iscsiadm(self, iscsi_properties, iscsi_command, **kwargs):
|
||||
check_exit_code = kwargs.pop('check_exit_code', 0)
|
||||
|
@ -380,7 +396,11 @@ class LibvirtISCSIVolumeDriver(LibvirtBaseVolumeDriver):
|
|||
# TODO(justinsb): This retry-with-delay is a pattern, move to utils?
|
||||
tries = 0
|
||||
disk_dev = disk_info['dev']
|
||||
while not os.path.exists(host_device):
|
||||
|
||||
# Check host_device only when transport is used, since otherwise it is
|
||||
# directly derived from properties. Only needed for unit tests
|
||||
while ((self._get_transport() != "default" and not host_device)
|
||||
or not os.path.exists(host_device)):
|
||||
if tries >= self.num_scan_tries:
|
||||
raise exception.NovaException(_("iSCSI device not found at %s")
|
||||
% (host_device))
|
||||
|
@ -392,8 +412,14 @@ class LibvirtISCSIVolumeDriver(LibvirtBaseVolumeDriver):
|
|||
# The rescan isn't documented as being necessary(?), but it helps
|
||||
self._run_iscsiadm(iscsi_properties, ("--rescan",))
|
||||
|
||||
# For offloaded open-iscsi transports, host_device cannot be
|
||||
# guessed unlike iscsi_tcp where it can be obtained from
|
||||
# properties, so try and get it again.
|
||||
if not host_device and self._get_transport() != "default":
|
||||
host_device = self._get_host_device(iscsi_properties)
|
||||
|
||||
tries = tries + 1
|
||||
if not os.path.exists(host_device):
|
||||
if not host_device or not os.path.exists(host_device):
|
||||
time.sleep(tries ** 2)
|
||||
|
||||
if tries != 0:
|
||||
|
@ -437,11 +463,14 @@ class LibvirtISCSIVolumeDriver(LibvirtBaseVolumeDriver):
|
|||
|
||||
# NOTE(vish): Only disconnect from the target if no luns from the
|
||||
# target are in use.
|
||||
device_prefix = ("/dev/disk/by-path/ip-%s-iscsi-%s-lun-" %
|
||||
device_byname = ("ip-%s-iscsi-%s-lun-" %
|
||||
(iscsi_properties['target_portal'],
|
||||
iscsi_properties['target_iqn']))
|
||||
devices = self.connection._get_all_block_devices()
|
||||
devices = [dev for dev in devices if dev.startswith(device_prefix)]
|
||||
devices = [dev for dev in devices if (device_byname in dev
|
||||
and
|
||||
dev.startswith(
|
||||
'/dev/disk/by-path/'))]
|
||||
if not devices:
|
||||
self._disconnect_from_iscsi_portal(iscsi_properties)
|
||||
elif host_device not in devices:
|
||||
|
@ -507,6 +536,10 @@ class LibvirtISCSIVolumeDriver(LibvirtBaseVolumeDriver):
|
|||
entry_ip_iqn = entry.split("-lun-")[0]
|
||||
if entry_ip_iqn[:3] == "ip-":
|
||||
entry_ip_iqn = entry_ip_iqn[3:]
|
||||
elif entry_ip_iqn[:4] == "pci-":
|
||||
# Look at an offset of len('pci-0000:00:00.0')
|
||||
offset = entry_ip_iqn.find("ip-", 16, 21)
|
||||
entry_ip_iqn = entry_ip_iqn[(offset + 3):]
|
||||
if (ip_iqn != entry_ip_iqn):
|
||||
continue
|
||||
entry_real_path = os.path.realpath("/dev/disk/by-path/%s" %
|
||||
|
@ -636,7 +669,13 @@ class LibvirtISCSIVolumeDriver(LibvirtBaseVolumeDriver):
|
|||
devices = list(os.walk('/dev/disk/by-path'))[0][-1]
|
||||
except IndexError:
|
||||
return []
|
||||
return [entry for entry in devices if entry.startswith("ip-")]
|
||||
iscsi_devs = []
|
||||
for entry in devices:
|
||||
if (entry.startswith("ip-") or
|
||||
(entry.startswith('pci-') and 'ip-' in entry)):
|
||||
iscsi_devs.append(entry)
|
||||
|
||||
return iscsi_devs
|
||||
|
||||
def _delete_mpath(self, iscsi_properties, multipath_device, ips_iqns):
|
||||
entries = self._get_iscsi_devices()
|
||||
|
@ -700,14 +739,27 @@ class LibvirtISCSIVolumeDriver(LibvirtBaseVolumeDriver):
|
|||
def _rescan_multipath(self):
|
||||
self._run_multipath(['-r'], check_exit_code=[0, 1, 21])
|
||||
|
||||
def _get_host_device(self, iscsi_properties):
|
||||
return ("/dev/disk/by-path/ip-%s-iscsi-%s-lun-%s" %
|
||||
(iscsi_properties['target_portal'],
|
||||
iscsi_properties['target_iqn'],
|
||||
iscsi_properties.get('target_lun', 0)))
|
||||
def _get_host_device(self, transport_properties):
|
||||
"""Find device path in devtemfs."""
|
||||
device = ("ip-%s-iscsi-%s-lun-%s" %
|
||||
(transport_properties['target_portal'],
|
||||
transport_properties['target_iqn'],
|
||||
transport_properties.get('target_lun', 0)))
|
||||
if self._get_transport() == "default":
|
||||
return ("/dev/disk/by-path/%s" % device)
|
||||
else:
|
||||
host_device = None
|
||||
look_for_device = glob.glob('/dev/disk/by-path/*%s' % device)
|
||||
if look_for_device:
|
||||
host_device = look_for_device[0]
|
||||
return host_device
|
||||
|
||||
def _reconnect(self, iscsi_properties):
|
||||
self._run_iscsiadm(iscsi_properties, ('--op', 'new'))
|
||||
# Note: iscsiadm does not support changing iface.iscsi_ifacename
|
||||
# via --op update, so we do this at creation time
|
||||
self._run_iscsiadm(iscsi_properties,
|
||||
('--interface', self._get_transport(),
|
||||
'--op', 'new'))
|
||||
|
||||
|
||||
class LibvirtISERVolumeDriver(LibvirtISCSIVolumeDriver):
|
||||
|
@ -717,6 +769,9 @@ class LibvirtISERVolumeDriver(LibvirtISCSIVolumeDriver):
|
|||
self.num_scan_tries = CONF.libvirt.num_iser_scan_tries
|
||||
self.use_multipath = CONF.libvirt.iser_use_multipath
|
||||
|
||||
def _get_transport(self):
|
||||
return 'iser'
|
||||
|
||||
def _get_multipath_iqn(self, multipath_device):
|
||||
entries = self._get_iscsi_devices()
|
||||
for entry in entries:
|
||||
|
@ -726,22 +781,6 @@ class LibvirtISERVolumeDriver(LibvirtISCSIVolumeDriver):
|
|||
return entry.split("iser-")[1].split("-lun")[0]
|
||||
return None
|
||||
|
||||
def _get_host_device(self, iser_properties):
|
||||
time.sleep(1)
|
||||
host_device = None
|
||||
device = ("ip-%s-iscsi-%s-lun-%s" %
|
||||
(iser_properties['target_portal'],
|
||||
iser_properties['target_iqn'],
|
||||
iser_properties.get('target_lun', 0)))
|
||||
look_for_device = glob.glob('/dev/disk/by-path/*%s' % device)
|
||||
if look_for_device:
|
||||
host_device = look_for_device[0]
|
||||
return host_device
|
||||
|
||||
def _reconnect(self, iser_properties):
|
||||
self._run_iscsiadm(iser_properties,
|
||||
('--interface', 'iser', '--op', 'new'))
|
||||
|
||||
|
||||
class LibvirtNFSVolumeDriver(LibvirtBaseVolumeDriver):
|
||||
"""Class implements libvirt part of volume driver for NFS."""
|
||||
|
|
Loading…
Reference in New Issue