Fix multipath device discovery when UFN is enabled.

This currently returns an invalid path of `/dev/mapper/${WWID}`
when UFN is enabled leading to failures later on when we attempt to
use the device.

The output of `multipath -l ${path}` or `multipath -l ${wwid}`
should always list the correct device identifier to use with this
path as the first word on the first line.

The same change has been suggested for both Cinder [1] and Nova [2]
as they are also susceptible to this issue.

[1] https://review.openstack.org/#/c/170157/
[2] https://review.openstack.org/#/c/169873/

Change-Id: I4a2d0e6ba4522ebfa0c50be49f7039f6e4617ae8
Closes-Bug: 1401799
This commit is contained in:
Lee Yarwood 2015-04-02 19:24:07 +01:00
parent fa22662d2d
commit 7643a16588
2 changed files with 47 additions and 19 deletions
os_brick
initiator
tests/initiator

@ -28,6 +28,7 @@ from os_brick.openstack.common import log as logging
LOG = logging.getLogger(__name__)
MULTIPATH_ERROR_REGEX = re.compile("\w{3} \d+ \d\d:\d\d:\d\d \|.*$")
MULTIPATH_WWID_REGEX = re.compile("\((?P<wwid>.+)\)")
class LinuxSCSI(executor.Executor):
@ -150,21 +151,26 @@ class LinuxSCSI(executor.Executor):
lines = [line for line in lines
if not re.match(MULTIPATH_ERROR_REGEX, line)]
if lines:
line = lines[0]
info = line.split(" ")
# device line output is different depending
# on /etc/multipath.conf settings.
if info[1][:2] == "dm":
mdev = "/dev/%s" % info[1]
mdev_id = info[0]
elif info[2][:2] == "dm":
mdev = "/dev/%s" % info[2]
mdev_id = info[1].replace('(', '')
mdev_id = mdev_id.replace(')', '')
if mdev is None:
LOG.warning(_LW("Couldn't find multipath device %(line)s"),
{'line': line})
# Use the device name, be it the WWID, mpathN or custom alias
# of a device to build the device path. This should be the
# first item on the first line of output from `multipath -l
# ${path}` or `multipath -l ${wwid}`..
mdev_name = lines[0].split(" ")[0]
mdev = '/dev/mapper/%s' % mdev_name
# Find the WWID for the LUN if we are using mpathN or aliases.
wwid_search = MULTIPATH_WWID_REGEX.search(lines[0])
if wwid_search is not None:
mdev_id = wwid_search.group('wwid')
else:
mdev_id = mdev_name
# Confirm that the device is present.
try:
os.stat(mdev)
except OSError:
LOG.warn(_LW("Couldn't find multipath device %s"), mdev)
return None
LOG.debug("Found multipath device = %(mdev)s",
@ -188,6 +194,7 @@ class LinuxSCSI(executor.Executor):
if mdev is not None:
info = {"device": mdev,
"id": mdev_id,
"name": mdev_name,
"devices": devices}
return info
return None

@ -12,6 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import os
import os.path
import string
@ -29,6 +30,7 @@ class LinuxSCSITestCase(base.TestCase):
super(LinuxSCSITestCase, self).setUp()
self.cmds = []
mock.patch.object(os.path, 'realpath', return_value='/dev/sdc').start()
mock.patch.object(os, 'stat', returns=os.stat(__file__)).start()
self.addCleanup(mock.patch.stopall)
self.linuxscsi = linuxscsi.LinuxSCSI(None, execute=self.fake_execute)
@ -100,7 +102,7 @@ class LinuxSCSITestCase(base.TestCase):
('multipath -f 350002ac20398383d'), ]
self.assertEqual(expected_commands, self.cmds)
def test_find_multipath_device_3par(self):
def test_find_multipath_device_3par_ufn(self):
def fake_execute(*cmd, **kwargs):
out = ("mpath6 (350002ac20398383d) dm-3 3PARdata,VV\n"
"size=2.0G features='0' hwhandler='0' wp=rw\n"
@ -114,7 +116,11 @@ class LinuxSCSITestCase(base.TestCase):
info = self.linuxscsi.find_multipath_device('/dev/sde')
LOG.error("info = %s" % info)
self.assertEqual("/dev/dm-3", info["device"])
self.assertEqual("350002ac20398383d", info["id"])
self.assertEqual("mpath6", info["name"])
self.assertEqual("/dev/mapper/mpath6", info["device"])
self.assertEqual("/dev/sde", info['devices'][0]['device'])
self.assertEqual("0", info['devices'][0]['host'])
self.assertEqual("0", info['devices'][0]['id'])
@ -145,7 +151,12 @@ class LinuxSCSITestCase(base.TestCase):
info = self.linuxscsi.find_multipath_device('/dev/sde')
LOG.error("info = %s" % info)
self.assertEqual("/dev/dm-2", info["device"])
self.assertEqual("36005076da00638089c000000000004d5", info["id"])
self.assertEqual("36005076da00638089c000000000004d5", info["name"])
self.assertEqual("/dev/mapper/36005076da00638089c000000000004d5",
info["device"])
self.assertEqual("/dev/sde", info['devices'][0]['device'])
self.assertEqual("6", info['devices'][0]['host'])
self.assertEqual("0", info['devices'][0]['channel'])
@ -173,7 +184,12 @@ class LinuxSCSITestCase(base.TestCase):
info = self.linuxscsi.find_multipath_device('/dev/sdd')
LOG.error("info = %s" % info)
self.assertEqual("/dev/dm-2", info["device"])
self.assertEqual("36005076303ffc48e0000000000000101", info["id"])
self.assertEqual("36005076303ffc48e0000000000000101", info["name"])
self.assertEqual("/dev/mapper/36005076303ffc48e0000000000000101",
info["device"])
self.assertEqual("/dev/sdd", info['devices'][0]['device'])
self.assertEqual("6", info['devices'][0]['host'])
self.assertEqual("0", info['devices'][0]['channel'])
@ -202,7 +218,12 @@ class LinuxSCSITestCase(base.TestCase):
info = self.linuxscsi.find_multipath_device('/dev/sdd')
LOG.error("info = %s" % info)
self.assertEqual("/dev/dm-2", info["device"])
self.assertEqual("36005076303ffc48e0000000000000101", info["id"])
self.assertEqual("36005076303ffc48e0000000000000101", info["name"])
self.assertEqual("/dev/mapper/36005076303ffc48e0000000000000101",
info["device"])
self.assertEqual("/dev/sdd", info['devices'][0]['device'])
self.assertEqual("6", info['devices'][0]['host'])
self.assertEqual("0", info['devices'][0]['channel'])