1432c369fb
When doing multipathed Fibre Channel we may end up not flushing the volume when disconnecting it if didn't find a multipath when doing the connection. The issue is caused by the `connect_volume` call returning a real path instead of a symlink and us passing the wrong device_info to the _remove_devices method, which makes the disconnect code incorrectly identify when a multipath was used to decide whether to do a flush or not. This patch fixes these things in order to flush the individual path when necessary. Closes-Bug: #1897787 Change-Id: Ie3266c81b3c6bb5a2c213a12410539d404d1febe
1164 lines
54 KiB
Python
1164 lines
54 KiB
Python
# (c) Copyright 2013 Hewlett-Packard Development Company, L.P.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
import os
|
|
import os.path
|
|
import textwrap
|
|
from unittest import mock
|
|
|
|
import ddt
|
|
from oslo_concurrency import processutils as putils
|
|
from oslo_log import log as logging
|
|
|
|
from os_brick import exception
|
|
from os_brick.initiator import linuxscsi
|
|
from os_brick.tests import base
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
@ddt.ddt
|
|
class LinuxSCSITestCase(base.TestCase):
|
|
def setUp(self):
|
|
super(LinuxSCSITestCase, self).setUp()
|
|
self.cmds = []
|
|
self.realpath = os.path.realpath
|
|
self.mock_object(os.path, 'realpath', return_value='/dev/sdc')
|
|
self.mock_object(os, 'stat', returns=os.stat(__file__))
|
|
self.linuxscsi = linuxscsi.LinuxSCSI(None, execute=self.fake_execute)
|
|
|
|
def fake_execute(self, *cmd, **kwargs):
|
|
self.cmds.append(" ".join(cmd))
|
|
return "", None
|
|
|
|
def test_echo_scsi_command(self):
|
|
self.linuxscsi.echo_scsi_command("/some/path", "1")
|
|
expected_commands = ['tee -a /some/path']
|
|
self.assertEqual(expected_commands, self.cmds)
|
|
|
|
@mock.patch.object(os.path, 'realpath')
|
|
def test_get_name_from_path(self, realpath_mock):
|
|
device_name = "/dev/sdc"
|
|
realpath_mock.return_value = device_name
|
|
disk_path = ("/dev/disk/by-path/ip-10.10.220.253:3260-"
|
|
"iscsi-iqn.2000-05.com.3pardata:21810002ac00383d-lun-0")
|
|
name = self.linuxscsi.get_name_from_path(disk_path)
|
|
self.assertEqual(device_name, name)
|
|
disk_path = ("/dev/disk/by-path/pci-0000:00:00.0-ip-10.9.8.7:3260-"
|
|
"iscsi-iqn.2000-05.com.openstack:2180002ac00383d-lun-0")
|
|
name = self.linuxscsi.get_name_from_path(disk_path)
|
|
self.assertEqual(device_name, name)
|
|
realpath_mock.return_value = "bogus"
|
|
name = self.linuxscsi.get_name_from_path(disk_path)
|
|
self.assertIsNone(name)
|
|
|
|
@mock.patch.object(os.path, 'exists', return_value=False)
|
|
def test_remove_scsi_device(self, exists_mock):
|
|
self.linuxscsi.remove_scsi_device("/dev/sdc")
|
|
expected_commands = []
|
|
self.assertEqual(expected_commands, self.cmds)
|
|
exists_mock.return_value = True
|
|
self.linuxscsi.remove_scsi_device("/dev/sdc")
|
|
expected_commands = [
|
|
('blockdev --flushbufs /dev/sdc'),
|
|
('tee -a /sys/block/sdc/device/delete')]
|
|
self.assertEqual(expected_commands, self.cmds)
|
|
|
|
@mock.patch.object(linuxscsi.LinuxSCSI, 'echo_scsi_command')
|
|
@mock.patch.object(linuxscsi.LinuxSCSI, 'flush_device_io')
|
|
@mock.patch.object(os.path, 'exists', return_value=True)
|
|
def test_remove_scsi_device_force(self, exists_mock, flush_mock,
|
|
echo_mock):
|
|
"""With force we'll always call delete even if flush fails."""
|
|
exc = exception.ExceptionChainer()
|
|
flush_mock.side_effect = Exception()
|
|
echo_mock.side_effect = Exception()
|
|
device = '/dev/sdc'
|
|
|
|
self.linuxscsi.remove_scsi_device(device, force=True, exc=exc)
|
|
# The context manager has caught the exceptions
|
|
self.assertTrue(exc)
|
|
flush_mock.assert_called_once_with(device)
|
|
echo_mock.assert_called_once_with('/sys/block/sdc/device/delete', '1')
|
|
|
|
@mock.patch.object(os.path, 'exists', return_value=False)
|
|
def test_remove_scsi_device_no_flush(self, exists_mock):
|
|
self.linuxscsi.remove_scsi_device("/dev/sdc")
|
|
expected_commands = []
|
|
self.assertEqual(expected_commands, self.cmds)
|
|
exists_mock.return_value = True
|
|
self.linuxscsi.remove_scsi_device("/dev/sdc", flush=False)
|
|
expected_commands = [('tee -a /sys/block/sdc/device/delete')]
|
|
self.assertEqual(expected_commands, self.cmds)
|
|
|
|
@mock.patch('os_brick.utils._time_sleep')
|
|
@mock.patch('os.path.exists', return_value=True)
|
|
def test_wait_for_volumes_removal_failure(self, exists_mock, sleep_mock):
|
|
retries = 61
|
|
names = ('sda', 'sdb')
|
|
self.assertRaises(exception.VolumePathNotRemoved,
|
|
self.linuxscsi.wait_for_volumes_removal, names)
|
|
exists_mock.assert_has_calls([mock.call('/dev/' + name)
|
|
for name in names] * retries)
|
|
self.assertEqual(retries - 1, sleep_mock.call_count)
|
|
|
|
@mock.patch('os_brick.utils._time_sleep')
|
|
@mock.patch('os.path.exists', side_effect=(True, True, False, False))
|
|
def test_wait_for_volumes_removal_retry(self, exists_mock, sleep_mock):
|
|
names = ('sda', 'sdb')
|
|
self.linuxscsi.wait_for_volumes_removal(names)
|
|
exists_mock.assert_has_calls([mock.call('/dev/' + name)
|
|
for name in names] * 2)
|
|
self.assertEqual(1, sleep_mock.call_count)
|
|
|
|
def test_flush_multipath_device(self):
|
|
dm_map_name = '3600d0230000000000e13955cc3757800'
|
|
with mock.patch.object(self.linuxscsi, '_execute') as exec_mock:
|
|
self.linuxscsi.flush_multipath_device(dm_map_name)
|
|
|
|
exec_mock.assert_called_once_with(
|
|
'multipath', '-f', dm_map_name, run_as_root=True, attempts=3,
|
|
timeout=300, interval=10, root_helper=self.linuxscsi._root_helper)
|
|
|
|
def test_get_scsi_wwn(self):
|
|
fake_path = '/dev/disk/by-id/somepath'
|
|
fake_wwn = '1234567890'
|
|
|
|
def fake_execute(*cmd, **kwargs):
|
|
return fake_wwn, None
|
|
|
|
self.linuxscsi._execute = fake_execute
|
|
wwn = self.linuxscsi.get_scsi_wwn(fake_path)
|
|
self.assertEqual(fake_wwn, wwn)
|
|
|
|
@mock.patch('six.moves.builtins.open')
|
|
def test_get_dm_name(self, open_mock):
|
|
dm_map_name = '3600d0230000000000e13955cc3757800'
|
|
cm_open = open_mock.return_value.__enter__.return_value
|
|
cm_open.read.return_value = dm_map_name
|
|
res = self.linuxscsi.get_dm_name('dm-0')
|
|
self.assertEqual(dm_map_name, res)
|
|
open_mock.assert_called_once_with('/sys/block/dm-0/dm/name')
|
|
|
|
@mock.patch('six.moves.builtins.open', side_effect=IOError)
|
|
def test_get_dm_name_failure(self, open_mock):
|
|
self.assertEqual('', self.linuxscsi.get_dm_name('dm-0'))
|
|
|
|
@mock.patch('glob.glob', side_effect=[[], ['/sys/block/sda/holders/dm-9']])
|
|
def test_find_sysfs_multipath_dm(self, glob_mock):
|
|
device_names = ('sda', 'sdb')
|
|
res = self.linuxscsi.find_sysfs_multipath_dm(device_names)
|
|
self.assertEqual('dm-9', res)
|
|
glob_mock.assert_has_calls([mock.call('/sys/block/sda/holders/dm-*'),
|
|
mock.call('/sys/block/sdb/holders/dm-*')])
|
|
|
|
@mock.patch('glob.glob', return_value=[])
|
|
def test_find_sysfs_multipath_dm_not_found(self, glob_mock):
|
|
device_names = ('sda', 'sdb')
|
|
res = self.linuxscsi.find_sysfs_multipath_dm(device_names)
|
|
self.assertIsNone(res)
|
|
glob_mock.assert_has_calls([mock.call('/sys/block/sda/holders/dm-*'),
|
|
mock.call('/sys/block/sdb/holders/dm-*')])
|
|
|
|
@mock.patch.object(linuxscsi.LinuxSCSI, '_execute')
|
|
@mock.patch('os.path.exists', return_value=True)
|
|
def test_flush_device_io(self, exists_mock, exec_mock):
|
|
device = '/dev/sda'
|
|
self.linuxscsi.flush_device_io(device)
|
|
exists_mock.assert_called_once_with(device)
|
|
exec_mock.assert_called_once_with(
|
|
'blockdev', '--flushbufs', device, run_as_root=True, attempts=3,
|
|
timeout=300, interval=10, root_helper=self.linuxscsi._root_helper)
|
|
|
|
@mock.patch('os.path.exists', return_value=False)
|
|
def test_flush_device_io_non_existent(self, exists_mock):
|
|
device = '/dev/sda'
|
|
self.linuxscsi.flush_device_io(device)
|
|
exists_mock.assert_called_once_with(device)
|
|
|
|
@mock.patch.object(os.path, 'exists', return_value=True)
|
|
def test_find_multipath_device_path(self, exists_mock):
|
|
fake_wwn = '1234567890'
|
|
found_path = self.linuxscsi.find_multipath_device_path(fake_wwn)
|
|
expected_path = '/dev/disk/by-id/dm-uuid-mpath-%s' % fake_wwn
|
|
self.assertEqual(expected_path, found_path)
|
|
|
|
@mock.patch('os_brick.utils._time_sleep')
|
|
@mock.patch.object(os.path, 'exists')
|
|
def test_find_multipath_device_path_mapper(self, exists_mock, sleep_mock):
|
|
# the wait loop tries 3 times before it gives up
|
|
# we want to test failing to find the
|
|
# /dev/disk/by-id/dm-uuid-mpath-<WWN> path
|
|
# but finding the
|
|
# /dev/mapper/<WWN> path
|
|
exists_mock.side_effect = [False, False, False, True]
|
|
fake_wwn = '1234567890'
|
|
found_path = self.linuxscsi.find_multipath_device_path(fake_wwn)
|
|
expected_path = '/dev/mapper/%s' % fake_wwn
|
|
self.assertEqual(expected_path, found_path)
|
|
self.assertTrue(sleep_mock.called)
|
|
|
|
@mock.patch.object(os.path, 'exists', return_value=False)
|
|
@mock.patch('os_brick.utils._time_sleep')
|
|
def test_find_multipath_device_path_fail(self, exists_mock, sleep_mock):
|
|
fake_wwn = '1234567890'
|
|
found_path = self.linuxscsi.find_multipath_device_path(fake_wwn)
|
|
self.assertIsNone(found_path)
|
|
|
|
@mock.patch.object(os.path, 'exists', return_value=False)
|
|
@mock.patch('os_brick.utils._time_sleep')
|
|
def test_wait_for_path_not_found(self, exists_mock, sleep_mock):
|
|
path = "/dev/disk/by-id/dm-uuid-mpath-%s" % '1234567890'
|
|
self.assertRaisesRegex(exception.VolumeDeviceNotFound,
|
|
r'Volume device not found at %s' % path,
|
|
self.linuxscsi.wait_for_path,
|
|
path)
|
|
|
|
@ddt.data({'do_raise': False, 'force': False},
|
|
{'do_raise': True, 'force': True})
|
|
@ddt.unpack
|
|
@mock.patch.object(linuxscsi.LinuxSCSI, '_remove_scsi_symlinks')
|
|
@mock.patch.object(linuxscsi.LinuxSCSI, 'flush_multipath_device')
|
|
@mock.patch.object(linuxscsi.LinuxSCSI, 'get_dm_name')
|
|
@mock.patch.object(linuxscsi.LinuxSCSI, 'find_sysfs_multipath_dm')
|
|
@mock.patch.object(linuxscsi.LinuxSCSI, 'wait_for_volumes_removal')
|
|
@mock.patch('os_brick.initiator.linuxscsi.LinuxSCSI.remove_scsi_device')
|
|
def test_remove_connection_multipath_complete(self, remove_mock, wait_mock,
|
|
find_dm_mock,
|
|
get_dm_name_mock,
|
|
flush_mp_mock,
|
|
remove_link_mock,
|
|
do_raise, force):
|
|
if do_raise:
|
|
flush_mp_mock.side_effect = Exception
|
|
devices_names = ('sda', 'sdb')
|
|
exc = exception.ExceptionChainer()
|
|
mp_name = self.linuxscsi.remove_connection(devices_names,
|
|
is_multipath=True,
|
|
force=mock.sentinel.Force,
|
|
exc=exc)
|
|
find_dm_mock.assert_called_once_with(devices_names)
|
|
get_dm_name_mock.assert_called_once_with(find_dm_mock.return_value)
|
|
flush_mp_mock.assert_called_once_with(get_dm_name_mock.return_value)
|
|
self.assertEqual(get_dm_name_mock.return_value if do_raise else None,
|
|
mp_name)
|
|
remove_mock.assert_has_calls([
|
|
mock.call('/dev/sda', mock.sentinel.Force, exc, False),
|
|
mock.call('/dev/sdb', mock.sentinel.Force, exc, False)])
|
|
wait_mock.assert_called_once_with(devices_names)
|
|
self.assertEqual(do_raise, bool(exc))
|
|
remove_link_mock.assert_called_once_with(devices_names)
|
|
|
|
@mock.patch.object(linuxscsi.LinuxSCSI, '_remove_scsi_symlinks')
|
|
@mock.patch.object(linuxscsi.LinuxSCSI, 'flush_multipath_device',
|
|
side_effect=Exception)
|
|
@mock.patch.object(linuxscsi.LinuxSCSI, 'get_dm_name')
|
|
@mock.patch.object(linuxscsi.LinuxSCSI, 'find_sysfs_multipath_dm')
|
|
@mock.patch.object(linuxscsi.LinuxSCSI, 'wait_for_volumes_removal')
|
|
@mock.patch.object(linuxscsi.LinuxSCSI, 'remove_scsi_device')
|
|
def test_remove_connection_multipath_fail(self, remove_mock, wait_mock,
|
|
find_dm_mock, get_dm_name_mock,
|
|
flush_mp_mock, remove_link_mock):
|
|
flush_mp_mock.side_effect = exception.ExceptionChainer
|
|
devices_names = ('sda', 'sdb')
|
|
exc = exception.ExceptionChainer()
|
|
self.assertRaises(exception.ExceptionChainer,
|
|
self.linuxscsi.remove_connection,
|
|
devices_names, is_multipath=True,
|
|
force=False, exc=exc)
|
|
find_dm_mock.assert_called_once_with(devices_names)
|
|
get_dm_name_mock.assert_called_once_with(find_dm_mock.return_value)
|
|
flush_mp_mock.assert_called_once_with(get_dm_name_mock.return_value)
|
|
remove_mock.assert_not_called()
|
|
wait_mock.assert_not_called()
|
|
remove_link_mock.assert_not_called()
|
|
self.assertTrue(bool(exc))
|
|
|
|
@mock.patch.object(linuxscsi.LinuxSCSI, '_remove_scsi_symlinks')
|
|
@mock.patch.object(linuxscsi.LinuxSCSI, 'wait_for_volumes_removal')
|
|
@mock.patch.object(linuxscsi.LinuxSCSI, 'remove_scsi_device')
|
|
def test_remove_connection_singlepath(self, remove_mock, wait_mock,
|
|
remove_link_mock):
|
|
devices_names = ('sda', 'sdb')
|
|
exc = exception.ExceptionChainer()
|
|
self.linuxscsi.remove_connection(devices_names, is_multipath=False,
|
|
force=mock.sentinel.Force,
|
|
exc=exc)
|
|
remove_mock.assert_has_calls(
|
|
[mock.call('/dev/sda', mock.sentinel.Force, exc, False),
|
|
mock.call('/dev/sdb', mock.sentinel.Force, exc, False)])
|
|
wait_mock.assert_called_once_with(devices_names)
|
|
remove_link_mock.assert_called_once_with(devices_names)
|
|
|
|
@mock.patch.object(linuxscsi.LinuxSCSI, '_remove_scsi_symlinks')
|
|
@mock.patch.object(linuxscsi.LinuxSCSI, 'wait_for_volumes_removal')
|
|
@mock.patch.object(linuxscsi.LinuxSCSI, 'remove_scsi_device')
|
|
def test_remove_connection_singlepath_used(self, remove_mock, wait_mock,
|
|
remove_link_mock):
|
|
devices_names = ('sda', 'sdb')
|
|
exc = exception.ExceptionChainer()
|
|
|
|
# realpath was mocked on test setup
|
|
with mock.patch('os.path.realpath', side_effect=self.realpath):
|
|
self.linuxscsi.remove_connection(devices_names, is_multipath=True,
|
|
force=mock.sentinel.Force,
|
|
exc=exc, path_used='/dev/sdb',
|
|
was_multipath=False)
|
|
remove_mock.assert_has_calls(
|
|
[mock.call('/dev/sda', mock.sentinel.Force, exc, False),
|
|
mock.call('/dev/sdb', mock.sentinel.Force, exc, True)])
|
|
wait_mock.assert_called_once_with(devices_names)
|
|
remove_link_mock.assert_called_once_with(devices_names)
|
|
|
|
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"
|
|
"`-+- policy='round-robin 0' prio=-1 status=active\n"
|
|
" |- 0:0:0:1 sde 8:64 active undef running\n"
|
|
" `- 2:0:0:1 sdf 8:80 active undef running\n"
|
|
)
|
|
return out, None
|
|
|
|
self.linuxscsi._execute = fake_execute
|
|
|
|
info = self.linuxscsi.find_multipath_device('/dev/sde')
|
|
|
|
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'])
|
|
self.assertEqual("0", info['devices'][0]['channel'])
|
|
self.assertEqual("1", info['devices'][0]['lun'])
|
|
|
|
self.assertEqual("/dev/sdf", info['devices'][1]['device'])
|
|
self.assertEqual("2", info['devices'][1]['host'])
|
|
self.assertEqual("0", info['devices'][1]['id'])
|
|
self.assertEqual("0", info['devices'][1]['channel'])
|
|
self.assertEqual("1", info['devices'][1]['lun'])
|
|
|
|
def test_find_multipath_device_svc(self):
|
|
def fake_execute(*cmd, **kwargs):
|
|
out = ("36005076da00638089c000000000004d5 dm-2 IBM,2145\n"
|
|
"size=954M features='1 queue_if_no_path' hwhandler='0'"
|
|
" wp=rw\n"
|
|
"|-+- policy='round-robin 0' prio=-1 status=active\n"
|
|
"| |- 6:0:2:0 sde 8:64 active undef running\n"
|
|
"| `- 6:0:4:0 sdg 8:96 active undef running\n"
|
|
"`-+- policy='round-robin 0' prio=-1 status=enabled\n"
|
|
" |- 6:0:3:0 sdf 8:80 active undef running\n"
|
|
" `- 6:0:5:0 sdh 8:112 active undef running\n"
|
|
)
|
|
return out, None
|
|
|
|
self.linuxscsi._execute = fake_execute
|
|
|
|
info = self.linuxscsi.find_multipath_device('/dev/sde')
|
|
|
|
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'])
|
|
self.assertEqual("2", info['devices'][0]['id'])
|
|
self.assertEqual("0", info['devices'][0]['lun'])
|
|
|
|
self.assertEqual("/dev/sdf", info['devices'][2]['device'])
|
|
self.assertEqual("6", info['devices'][2]['host'])
|
|
self.assertEqual("0", info['devices'][2]['channel'])
|
|
self.assertEqual("3", info['devices'][2]['id'])
|
|
self.assertEqual("0", info['devices'][2]['lun'])
|
|
|
|
def test_find_multipath_device_ds8000(self):
|
|
def fake_execute(*cmd, **kwargs):
|
|
out = ("36005076303ffc48e0000000000000101 dm-2 IBM,2107900\n"
|
|
"size=1.0G features='1 queue_if_no_path' hwhandler='0'"
|
|
" wp=rw\n"
|
|
"`-+- policy='round-robin 0' prio=-1 status=active\n"
|
|
" |- 6:0:2:0 sdd 8:64 active undef running\n"
|
|
" `- 6:1:0:3 sdc 8:32 active undef running\n"
|
|
)
|
|
return out, None
|
|
|
|
self.linuxscsi._execute = fake_execute
|
|
|
|
info = self.linuxscsi.find_multipath_device('/dev/sdd')
|
|
|
|
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'])
|
|
self.assertEqual("2", info['devices'][0]['id'])
|
|
self.assertEqual("0", info['devices'][0]['lun'])
|
|
|
|
self.assertEqual("/dev/sdc", info['devices'][1]['device'])
|
|
self.assertEqual("6", info['devices'][1]['host'])
|
|
self.assertEqual("1", info['devices'][1]['channel'])
|
|
self.assertEqual("0", info['devices'][1]['id'])
|
|
self.assertEqual("3", info['devices'][1]['lun'])
|
|
|
|
def test_find_multipath_device_with_error(self):
|
|
def fake_execute(*cmd, **kwargs):
|
|
out = ("Oct 13 10:24:01 | /lib/udev/scsi_id exited with 1\n"
|
|
"36005076303ffc48e0000000000000101 dm-2 IBM,2107900\n"
|
|
"size=1.0G features='1 queue_if_no_path' hwhandler='0'"
|
|
" wp=rw\n"
|
|
"`-+- policy='round-robin 0' prio=-1 status=active\n"
|
|
" |- 6:0:2:0 sdd 8:64 active undef running\n"
|
|
" `- 6:1:0:3 sdc 8:32 active undef running\n"
|
|
)
|
|
return out, None
|
|
|
|
self.linuxscsi._execute = fake_execute
|
|
|
|
info = self.linuxscsi.find_multipath_device('/dev/sdd')
|
|
|
|
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'])
|
|
self.assertEqual("2", info['devices'][0]['id'])
|
|
self.assertEqual("0", info['devices'][0]['lun'])
|
|
|
|
self.assertEqual("/dev/sdc", info['devices'][1]['device'])
|
|
self.assertEqual("6", info['devices'][1]['host'])
|
|
self.assertEqual("1", info['devices'][1]['channel'])
|
|
self.assertEqual("0", info['devices'][1]['id'])
|
|
self.assertEqual("3", info['devices'][1]['lun'])
|
|
|
|
def test_find_multipath_device_with_multiple_errors(self):
|
|
def fake_execute(*cmd, **kwargs):
|
|
out = ("Jun 21 04:39:26 | 8:160: path wwid appears to have "
|
|
"changed. Using old wwid.\n\n"
|
|
"Jun 21 04:39:26 | 65:208: path wwid appears to have "
|
|
"changed. Using old wwid.\n\n"
|
|
"Jun 21 04:39:26 | 65:208: path wwid appears to have "
|
|
"changed. Using old wwid.\n"
|
|
"3624a93707edcfde1127040370004ee62 dm-84 PURE ,"
|
|
"FlashArray\n"
|
|
"size=100G features='0' hwhandler='0' wp=rw\n"
|
|
"`-+- policy='queue-length 0' prio=1 status=active\n"
|
|
" |- 8:0:0:9 sdaa 65:160 active ready running\n"
|
|
" `- 8:0:1:9 sdac 65:192 active ready running\n"
|
|
)
|
|
return out, None
|
|
|
|
self.linuxscsi._execute = fake_execute
|
|
|
|
info = self.linuxscsi.find_multipath_device('/dev/sdaa')
|
|
|
|
self.assertEqual("3624a93707edcfde1127040370004ee62", info["id"])
|
|
self.assertEqual("3624a93707edcfde1127040370004ee62", info["name"])
|
|
self.assertEqual("/dev/mapper/3624a93707edcfde1127040370004ee62",
|
|
info["device"])
|
|
|
|
self.assertEqual("/dev/sdaa", info['devices'][0]['device'])
|
|
self.assertEqual("8", info['devices'][0]['host'])
|
|
self.assertEqual("0", info['devices'][0]['channel'])
|
|
self.assertEqual("0", info['devices'][0]['id'])
|
|
self.assertEqual("9", info['devices'][0]['lun'])
|
|
|
|
self.assertEqual("/dev/sdac", info['devices'][1]['device'])
|
|
self.assertEqual("8", info['devices'][1]['host'])
|
|
self.assertEqual("0", info['devices'][1]['channel'])
|
|
self.assertEqual("1", info['devices'][1]['id'])
|
|
self.assertEqual("9", info['devices'][1]['lun'])
|
|
|
|
@mock.patch('os_brick.utils._time_sleep')
|
|
def test_wait_for_rw(self, mock_sleep):
|
|
lsblk_output = """3624a93709a738ed78583fd1200143029 (dm-2) 0
|
|
sdb 0
|
|
3624a93709a738ed78583fd120014724e (dm-1) 0
|
|
sdc 0
|
|
3624a93709a738ed78583fd120014a2bb (dm-0) 0
|
|
sdd 0
|
|
3624a93709a738ed78583fd1200143029 (dm-2) 0
|
|
sde 0
|
|
3624a93709a738ed78583fd120014724e (dm-1) 0
|
|
sdf 0
|
|
3624a93709a738ed78583fd120014a2bb (dm-0) 0
|
|
sdg 0
|
|
3624a93709a738ed78583fd1200143029 (dm-2) 0
|
|
sdh 0
|
|
3624a93709a738ed78583fd120014724e (dm-1) 0
|
|
sdi 0
|
|
3624a93709a738ed78583fd120014a2bb (dm-0) 0
|
|
sdj 0
|
|
3624a93709a738ed78583fd1200143029 (dm-2) 0
|
|
sdk 0
|
|
3624a93709a738ed78583fd120014724e (dm-1) 0
|
|
sdl 0
|
|
3624a93709a738ed78583fd120014a2bb (dm-0) 0
|
|
sdm 0
|
|
vda1 0
|
|
vdb 0
|
|
vdb1 0
|
|
loop0 0"""
|
|
|
|
mock_execute = mock.Mock()
|
|
mock_execute.return_value = (lsblk_output, None)
|
|
self.linuxscsi._execute = mock_execute
|
|
|
|
wwn = '3624a93709a738ed78583fd120014a2bb'
|
|
path = '/dev/disk/by-id/dm-uuid-mpath-' + wwn
|
|
|
|
# Ensure no exception is raised and no sleep is called
|
|
self.linuxscsi.wait_for_rw(wwn, path)
|
|
self.assertFalse(mock_sleep.called)
|
|
|
|
@mock.patch('os_brick.utils._time_sleep')
|
|
def test_wait_for_rw_needs_retry(self, mock_sleep):
|
|
lsblk_ro_output = """3624a93709a738ed78583fd1200143029 (dm-2) 0
|
|
sdb 0
|
|
3624a93709a738ed78583fd120014724e (dm-1) 0
|
|
sdc 0
|
|
3624a93709a738ed78583fd120014a2bb (dm-0) 0
|
|
sdd 0
|
|
3624a93709a738ed78583fd1200143029 (dm-2) 1
|
|
sde 0
|
|
3624a93709a738ed78583fd120014724e (dm-1) 0
|
|
sdf 0
|
|
3624a93709a738ed78583fd120014a2bb (dm-0) 0
|
|
sdg 0
|
|
3624a93709a738ed78583fd1200143029 (dm-2) 1
|
|
sdh 0
|
|
3624a93709a738ed78583fd120014724e (dm-1) 0
|
|
sdi 0
|
|
3624a93709a738ed78583fd120014a2bb (dm-0) 0
|
|
sdj 0
|
|
3624a93709a738ed78583fd1200143029 (dm-2) 1
|
|
sdk 0
|
|
3624a93709a738ed78583fd120014724e (dm-1) 0
|
|
sdl 0
|
|
3624a93709a738ed78583fd120014a2bb (dm-0) 0
|
|
sdm 0
|
|
vda1 0
|
|
vdb 0
|
|
vdb1 0
|
|
loop0 0"""
|
|
lsblk_rw_output = """3624a93709a738ed78583fd1200143029 (dm-2) 0
|
|
sdb 0
|
|
3624a93709a738ed78583fd120014724e (dm-1) 0
|
|
sdc 0
|
|
3624a93709a738ed78583fd120014a2bb (dm-0) 0
|
|
sdd 0
|
|
3624a93709a738ed78583fd1200143029 (dm-2) 0
|
|
sde 0
|
|
3624a93709a738ed78583fd120014724e (dm-1) 0
|
|
sdf 0
|
|
3624a93709a738ed78583fd120014a2bb (dm-0) 0
|
|
sdg 0
|
|
3624a93709a738ed78583fd1200143029 (dm-2) 0
|
|
sdh 0
|
|
3624a93709a738ed78583fd120014724e (dm-1) 0
|
|
sdi 0
|
|
3624a93709a738ed78583fd120014a2bb (dm-0) 0
|
|
sdj 0
|
|
3624a93709a738ed78583fd1200143029 (dm-2) 0
|
|
sdk 0
|
|
3624a93709a738ed78583fd120014724e (dm-1) 0
|
|
sdl 0
|
|
3624a93709a738ed78583fd120014a2bb (dm-0) 0
|
|
sdm 0
|
|
vda1 0
|
|
vdb 0
|
|
vdb1 0
|
|
loop0 0"""
|
|
mock_execute = mock.Mock()
|
|
mock_execute.side_effect = [(lsblk_ro_output, None),
|
|
('', None), # multipath -r output
|
|
(lsblk_rw_output, None)]
|
|
self.linuxscsi._execute = mock_execute
|
|
|
|
wwn = '3624a93709a738ed78583fd1200143029'
|
|
path = '/dev/disk/by-id/dm-uuid-mpath-' + wwn
|
|
|
|
self.linuxscsi.wait_for_rw(wwn, path)
|
|
self.assertEqual(1, mock_sleep.call_count)
|
|
|
|
@mock.patch('os_brick.utils._time_sleep')
|
|
def test_wait_for_rw_always_readonly(self, mock_sleep):
|
|
lsblk_output = """3624a93709a738ed78583fd1200143029 (dm-2) 0
|
|
sdb 0
|
|
3624a93709a738ed78583fd120014724e (dm-1) 0
|
|
sdc 0
|
|
3624a93709a738ed78583fd120014a2bb (dm-0) 1
|
|
sdd 0
|
|
3624a93709a738ed78583fd1200143029 (dm-2) 0
|
|
sde 0
|
|
3624a93709a738ed78583fd120014724e (dm-1) 0
|
|
sdf 0
|
|
3624a93709a738ed78583fd120014a2bb (dm-0) 1
|
|
sdg 0
|
|
3624a93709a738ed78583fd1200143029 (dm-2) 0
|
|
sdh 0
|
|
3624a93709a738ed78583fd120014724e (dm-1) 0
|
|
sdi 0
|
|
3624a93709a738ed78583fd120014a2bb (dm-0) 1
|
|
sdj 0
|
|
3624a93709a738ed78583fd1200143029 (dm-2) 0
|
|
sdk 0
|
|
3624a93709a738ed78583fd120014724e (dm-1) 0
|
|
sdl 0
|
|
3624a93709a738ed78583fd120014a2bb (dm-0) 1
|
|
sdm 0
|
|
vda1 0
|
|
vdb 0
|
|
vdb1 0
|
|
loop0 0"""
|
|
|
|
mock_execute = mock.Mock()
|
|
mock_execute.return_value = (lsblk_output, None)
|
|
self.linuxscsi._execute = mock_execute
|
|
|
|
wwn = '3624a93709a738ed78583fd120014a2bb'
|
|
path = '/dev/disk/by-id/dm-uuid-mpath-' + wwn
|
|
|
|
self.assertRaises(exception.BlockDeviceReadOnly,
|
|
self.linuxscsi.wait_for_rw,
|
|
wwn,
|
|
path)
|
|
|
|
self.assertEqual(4, mock_sleep.call_count)
|
|
|
|
def test_find_multipath_device_with_action(self):
|
|
def fake_execute(*cmd, **kwargs):
|
|
out = textwrap.dedent("""
|
|
create: 36005076303ffc48e0000000000000101 dm-2 IBM,2107900
|
|
size=1.0G features='1 queue_if_no_path' hwhandler='0'
|
|
wp=rw
|
|
`-+- policy='round-robin 0' prio=-1 status=active
|
|
|- 6:0:2:0 sdd 8:64 active undef running
|
|
`- 6:1:0:3 sdc 8:32 active undef running
|
|
""")
|
|
return out, None
|
|
|
|
self.linuxscsi._execute = fake_execute
|
|
info = self.linuxscsi.find_multipath_device('/dev/sdd')
|
|
LOG.error("Device info: %s", info)
|
|
|
|
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'])
|
|
self.assertEqual("2", info['devices'][0]['id'])
|
|
self.assertEqual("0", info['devices'][0]['lun'])
|
|
|
|
self.assertEqual("/dev/sdc", info['devices'][1]['device'])
|
|
self.assertEqual("6", info['devices'][1]['host'])
|
|
self.assertEqual("1", info['devices'][1]['channel'])
|
|
self.assertEqual("0", info['devices'][1]['id'])
|
|
self.assertEqual("3", info['devices'][1]['lun'])
|
|
|
|
def test_get_device_size(self):
|
|
mock_execute = mock.Mock()
|
|
self.linuxscsi._execute = mock_execute
|
|
size = '1024'
|
|
mock_execute.return_value = (size, None)
|
|
|
|
ret_size = self.linuxscsi.get_device_size('/dev/fake')
|
|
self.assertEqual(int(size), ret_size)
|
|
|
|
size = 'junk'
|
|
mock_execute.return_value = (size, None)
|
|
ret_size = self.linuxscsi.get_device_size('/dev/fake')
|
|
self.assertIsNone(ret_size)
|
|
|
|
size_bad = '1024\n'
|
|
size_good = 1024
|
|
mock_execute.return_value = (size_bad, None)
|
|
ret_size = self.linuxscsi.get_device_size('/dev/fake')
|
|
self.assertEqual(size_good, ret_size)
|
|
|
|
def test_multipath_reconfigure(self):
|
|
self.linuxscsi.multipath_reconfigure()
|
|
expected_commands = ['multipathd reconfigure']
|
|
self.assertEqual(expected_commands, self.cmds)
|
|
|
|
def test_multipath_resize_map(self):
|
|
wwn = '1234567890123456'
|
|
self.linuxscsi.multipath_resize_map(wwn)
|
|
expected_commands = ['multipathd resize map %s' % wwn]
|
|
self.assertEqual(expected_commands, self.cmds)
|
|
|
|
@mock.patch.object(linuxscsi.LinuxSCSI, 'find_multipath_device_path')
|
|
@mock.patch.object(linuxscsi.LinuxSCSI, 'get_scsi_wwn')
|
|
@mock.patch.object(linuxscsi.LinuxSCSI, 'get_device_size')
|
|
@mock.patch.object(linuxscsi.LinuxSCSI, 'get_device_info')
|
|
def test_extend_volume_no_mpath(self, mock_device_info,
|
|
mock_device_size,
|
|
mock_scsi_wwn,
|
|
mock_find_mpath_path):
|
|
"""Test extending a volume where there is no multipath device."""
|
|
fake_device = {'host': '0',
|
|
'channel': '0',
|
|
'id': '0',
|
|
'lun': '1'}
|
|
mock_device_info.return_value = fake_device
|
|
|
|
first_size = 1024
|
|
second_size = 2048
|
|
|
|
mock_device_size.side_effect = [first_size, second_size]
|
|
wwn = '1234567890123456'
|
|
mock_scsi_wwn.return_value = wwn
|
|
mock_find_mpath_path.return_value = None
|
|
|
|
ret_size = self.linuxscsi.extend_volume(['/dev/fake'])
|
|
self.assertEqual(second_size, ret_size)
|
|
|
|
# because we don't mock out the echo_scsi_command
|
|
expected_cmds = ['tee -a /sys/bus/scsi/drivers/sd/0:0:0:1/rescan']
|
|
self.assertEqual(expected_cmds, self.cmds)
|
|
|
|
@mock.patch.object(linuxscsi.LinuxSCSI, 'find_multipath_device_path')
|
|
@mock.patch.object(linuxscsi.LinuxSCSI, 'get_scsi_wwn')
|
|
@mock.patch.object(linuxscsi.LinuxSCSI, 'get_device_size')
|
|
@mock.patch.object(linuxscsi.LinuxSCSI, 'get_device_info')
|
|
def test_extend_volume_with_mpath(self, mock_device_info,
|
|
mock_device_size,
|
|
mock_scsi_wwn,
|
|
mock_find_mpath_path):
|
|
"""Test extending a volume where there is a multipath device."""
|
|
mock_device_info.side_effect = [{'host': host,
|
|
'channel': '0',
|
|
'id': '0',
|
|
'lun': '1'} for host in ['0', '1']]
|
|
|
|
mock_device_size.side_effect = [1024, 2048, 1024, 2048, 1024, 2048]
|
|
wwn = '1234567890123456'
|
|
mock_scsi_wwn.return_value = wwn
|
|
mock_find_mpath_path.return_value = ('/dev/mapper/dm-uuid-mpath-%s' %
|
|
wwn)
|
|
|
|
ret_size = self.linuxscsi.extend_volume(['/dev/fake1', '/dev/fake2'],
|
|
use_multipath=True)
|
|
self.assertEqual(2048, ret_size)
|
|
|
|
# because we don't mock out the echo_scsi_command
|
|
expected_cmds = ['tee -a /sys/bus/scsi/drivers/sd/0:0:0:1/rescan',
|
|
'tee -a /sys/bus/scsi/drivers/sd/1:0:0:1/rescan',
|
|
'multipathd reconfigure',
|
|
'multipathd resize map %s' % wwn]
|
|
self.assertEqual(expected_cmds, self.cmds)
|
|
|
|
@mock.patch.object(linuxscsi.LinuxSCSI, 'multipath_resize_map')
|
|
@mock.patch.object(linuxscsi.LinuxSCSI, 'find_multipath_device_path')
|
|
@mock.patch.object(linuxscsi.LinuxSCSI, 'get_scsi_wwn')
|
|
@mock.patch.object(linuxscsi.LinuxSCSI, 'get_device_size')
|
|
@mock.patch.object(linuxscsi.LinuxSCSI, 'get_device_info')
|
|
def test_extend_volume_with_mpath_fail(self, mock_device_info,
|
|
mock_device_size,
|
|
mock_scsi_wwn,
|
|
mock_find_mpath_path,
|
|
mock_mpath_resize_map):
|
|
"""Test extending a volume where there is a multipath device fail."""
|
|
mock_device_info.side_effect = [{'host': host,
|
|
'channel': '0',
|
|
'id': '0',
|
|
'lun': '1'} for host in ['0', '1']]
|
|
|
|
mock_device_size.side_effect = [1024, 2048, 1024, 2048, 1024, 2048]
|
|
wwn = '1234567890123456'
|
|
mock_scsi_wwn.return_value = wwn
|
|
mock_find_mpath_path.return_value = ('/dev/mapper/dm-uuid-mpath-%s' %
|
|
wwn)
|
|
|
|
mock_mpath_resize_map.return_value = 'fail'
|
|
|
|
ret_size = self.linuxscsi.extend_volume(['/dev/fake1', '/dev/fake2'],
|
|
use_multipath=True)
|
|
self.assertIsNone(ret_size)
|
|
|
|
# because we don't mock out the echo_scsi_command
|
|
expected_cmds = ['tee -a /sys/bus/scsi/drivers/sd/0:0:0:1/rescan',
|
|
'tee -a /sys/bus/scsi/drivers/sd/1:0:0:1/rescan',
|
|
'multipathd reconfigure']
|
|
self.assertEqual(expected_cmds, self.cmds)
|
|
|
|
def test_process_lun_id_list(self):
|
|
lun_list = [2, 255, 88, 370, 5, 256]
|
|
result = self.linuxscsi.process_lun_id(lun_list)
|
|
expected = [2, 255, 88, '0x0172000000000000',
|
|
5, '0x0100000000000000']
|
|
|
|
self.assertEqual(expected, result)
|
|
|
|
def test_process_lun_id_single_val_make_hex(self):
|
|
lun_id = 499
|
|
result = self.linuxscsi.process_lun_id(lun_id)
|
|
expected = '0x01f3000000000000'
|
|
self.assertEqual(expected, result)
|
|
|
|
def test_process_lun_id_single_val_make_hex_border_case(self):
|
|
lun_id = 256
|
|
result = self.linuxscsi.process_lun_id(lun_id)
|
|
expected = '0x0100000000000000'
|
|
self.assertEqual(expected, result)
|
|
|
|
def test_process_lun_id_single_var_return(self):
|
|
lun_id = 13
|
|
result = self.linuxscsi.process_lun_id(lun_id)
|
|
expected = 13
|
|
self.assertEqual(expected, result)
|
|
|
|
@mock.patch('os_brick.privileged.rootwrap.execute', return_value=('', ''))
|
|
def test_is_multipath_running_default_executor(self, mock_exec):
|
|
res = linuxscsi.LinuxSCSI.is_multipath_running(False, None, mock_exec)
|
|
self.assertTrue(res)
|
|
mock_exec.assert_called_once_with(
|
|
'multipathd', 'show', 'status', run_as_root=True, root_helper=None)
|
|
|
|
@mock.patch('os_brick.privileged.rootwrap.execute')
|
|
def test_is_multipath_running_failure_exit_code_0(self, mock_exec):
|
|
mock_exec.return_value = ('error receiving packet', '')
|
|
self.assertRaises(putils.ProcessExecutionError,
|
|
linuxscsi.LinuxSCSI.is_multipath_running,
|
|
True, None, mock_exec)
|
|
mock_exec.assert_called_once_with(
|
|
'multipathd', 'show', 'status', run_as_root=True, root_helper=None)
|
|
|
|
def test_get_device_info(self):
|
|
ret = "[1:1:0:0] disk Vendor Array 0100 /dev/adevice\n"
|
|
with mock.patch.object(self.linuxscsi, '_execute') as exec_mock:
|
|
exec_mock.return_value = (ret, "")
|
|
info = self.linuxscsi.get_device_info('/dev/adevice')
|
|
|
|
exec_mock.assert_called_once_with('lsscsi')
|
|
self.assertEqual(info, {'channel': '1',
|
|
'device': '/dev/adevice',
|
|
'host': '1',
|
|
'id': '0',
|
|
'lun': '0'})
|
|
|
|
@mock.patch('six.moves.builtins.open')
|
|
def test_get_sysfs_wwn_mpath(self, open_mock):
|
|
wwn = '3600d0230000000000e13955cc3757800'
|
|
cm_open = open_mock.return_value.__enter__.return_value
|
|
cm_open.read.return_value = 'mpath-' + wwn
|
|
|
|
res = self.linuxscsi.get_sysfs_wwn(mock.sentinel.device_names, 'dm-1')
|
|
open_mock.assert_called_once_with('/sys/block/dm-1/dm/uuid')
|
|
self.assertEqual(wwn, res)
|
|
|
|
@mock.patch('glob.glob')
|
|
@mock.patch.object(linuxscsi.LinuxSCSI, 'get_sysfs_wwid')
|
|
def test_get_sysfs_wwn_single_designator(self, get_wwid_mock, glob_mock):
|
|
glob_mock.return_value = ['/dev/disk/by-id/scsi-wwid1',
|
|
'/dev/disk/by-id/scsi-wwid2']
|
|
get_wwid_mock.return_value = 'wwid1'
|
|
res = self.linuxscsi.get_sysfs_wwn(mock.sentinel.device_names)
|
|
self.assertEqual('wwid1', res)
|
|
glob_mock.assert_called_once_with('/dev/disk/by-id/scsi-*')
|
|
get_wwid_mock.assert_called_once_with(mock.sentinel.device_names)
|
|
|
|
@mock.patch('six.moves.builtins.open', side_effect=Exception)
|
|
@mock.patch('glob.glob')
|
|
@mock.patch.object(linuxscsi.LinuxSCSI, 'get_sysfs_wwid')
|
|
def test_get_sysfs_wwn_mpath_exc(self, get_wwid_mock, glob_mock,
|
|
open_mock):
|
|
glob_mock.return_value = ['/dev/disk/by-id/scsi-wwid1',
|
|
'/dev/disk/by-id/scsi-wwid2']
|
|
get_wwid_mock.return_value = 'wwid1'
|
|
res = self.linuxscsi.get_sysfs_wwn(mock.sentinel.device_names, 'dm-1')
|
|
open_mock.assert_called_once_with('/sys/block/dm-1/dm/uuid')
|
|
self.assertEqual('wwid1', res)
|
|
glob_mock.assert_called_once_with('/dev/disk/by-id/scsi-*')
|
|
get_wwid_mock.assert_called_once_with(mock.sentinel.device_names)
|
|
|
|
@mock.patch('os.listdir', return_value=['sda', 'sdd'])
|
|
@mock.patch('os.path.realpath', side_effect=('/other/path',
|
|
'/dev/dm-5',
|
|
'/dev/sda', '/dev/sdb'))
|
|
@mock.patch('os.path.islink', side_effect=(False,) + (True,) * 5)
|
|
@mock.patch('os.stat', side_effect=(False,) + (True,) * 4)
|
|
@mock.patch('glob.glob')
|
|
@mock.patch.object(linuxscsi.LinuxSCSI, 'get_sysfs_wwid')
|
|
def test_get_sysfs_wwn_multiple_designators(self, get_wwid_mock, glob_mock,
|
|
stat_mock, islink_mock,
|
|
realpath_mock, listdir_mock):
|
|
glob_mock.return_value = ['/dev/disk/by-id/scsi-fail-link',
|
|
'/dev/disk/by-id/scsi-fail-stat',
|
|
'/dev/disk/by-id/scsi-non-dev',
|
|
'/dev/disk/by-id/scsi-another-dm',
|
|
'/dev/disk/by-id/scsi-wwid1',
|
|
'/dev/disk/by-id/scsi-wwid2']
|
|
|
|
get_wwid_mock.return_value = 'pre-wwid'
|
|
devices = ['sdb', 'sdc']
|
|
res = self.linuxscsi.get_sysfs_wwn(devices)
|
|
self.assertEqual('wwid2', res)
|
|
glob_mock.assert_called_once_with('/dev/disk/by-id/scsi-*')
|
|
listdir_mock.assert_called_once_with('/sys/class/block/dm-5/slaves')
|
|
get_wwid_mock.assert_called_once_with(devices)
|
|
|
|
@mock.patch('os.listdir', side_effect=[['sda', 'sdb'], ['sdc', 'sdd']])
|
|
@mock.patch('os.path.realpath', side_effect=('/dev/sde',
|
|
'/dev/dm-5',
|
|
'/dev/dm-6'))
|
|
@mock.patch('os.path.islink', mock.Mock())
|
|
@mock.patch('os.stat', mock.Mock())
|
|
@mock.patch('glob.glob')
|
|
@mock.patch.object(linuxscsi.LinuxSCSI, 'get_sysfs_wwid', return_value='')
|
|
def test_get_sysfs_wwn_dm_link(self, get_wwid_mock, glob_mock,
|
|
realpath_mock, listdir_mock):
|
|
glob_mock.return_value = ['/dev/disk/by-id/scsi-wwid1',
|
|
'/dev/disk/by-id/scsi-another-dm',
|
|
'/dev/disk/by-id/scsi-our-dm']
|
|
|
|
devices = ['sdc', 'sdd']
|
|
res = self.linuxscsi.get_sysfs_wwn(devices)
|
|
self.assertEqual('our-dm', res)
|
|
glob_mock.assert_called_once_with('/dev/disk/by-id/scsi-*')
|
|
listdir_mock.assert_has_calls(
|
|
[mock.call('/sys/class/block/dm-5/slaves'),
|
|
mock.call('/sys/class/block/dm-6/slaves')])
|
|
get_wwid_mock.assert_called_once_with(devices)
|
|
|
|
@mock.patch('os.path.realpath', side_effect=('/dev/sda', '/dev/sdb'))
|
|
@mock.patch('os.path.islink', return_value=True)
|
|
@mock.patch('os.stat', return_value=True)
|
|
@mock.patch('glob.glob')
|
|
@mock.patch.object(linuxscsi.LinuxSCSI, 'get_sysfs_wwid')
|
|
def test_get_sysfs_wwn_not_found(self, get_wwid_mock, glob_mock, stat_mock,
|
|
islink_mock, realpath_mock):
|
|
glob_mock.return_value = ['/dev/disk/by-id/scsi-wwid1',
|
|
'/dev/disk/by-id/scsi-wwid2']
|
|
get_wwid_mock.return_value = 'pre-wwid'
|
|
devices = ['sdc']
|
|
res = self.linuxscsi.get_sysfs_wwn(devices)
|
|
self.assertEqual('', res)
|
|
glob_mock.assert_called_once_with('/dev/disk/by-id/scsi-*')
|
|
get_wwid_mock.assert_called_once_with(devices)
|
|
|
|
@mock.patch('glob.glob', return_value=[])
|
|
@mock.patch.object(linuxscsi.LinuxSCSI, 'get_sysfs_wwid')
|
|
def test_get_sysfs_wwn_no_links(self, get_wwid_mock, glob_mock):
|
|
get_wwid_mock.return_value = ''
|
|
devices = ['sdc']
|
|
res = self.linuxscsi.get_sysfs_wwn(devices)
|
|
self.assertEqual('', res)
|
|
glob_mock.assert_called_once_with('/dev/disk/by-id/scsi-*')
|
|
get_wwid_mock.assert_called_once_with(devices)
|
|
|
|
@ddt.data({'wwn_type': 't10.', 'num_val': '1'},
|
|
{'wwn_type': 'eui.', 'num_val': '2'},
|
|
{'wwn_type': 'naa.', 'num_val': '3'})
|
|
@ddt.unpack
|
|
@mock.patch('six.moves.builtins.open')
|
|
def test_get_sysfs_wwid(self, open_mock, wwn_type, num_val):
|
|
read_fail = mock.MagicMock()
|
|
read_fail.__enter__.return_value.read.side_effect = IOError
|
|
read_data = mock.MagicMock()
|
|
read_data.__enter__.return_value.read.return_value = (wwn_type +
|
|
'wwid1\n')
|
|
open_mock.side_effect = (IOError, read_fail, read_data)
|
|
|
|
res = self.linuxscsi.get_sysfs_wwid(['sda', 'sdb', 'sdc'])
|
|
self.assertEqual(num_val + 'wwid1', res)
|
|
open_mock.assert_has_calls([mock.call('/sys/block/sda/device/wwid'),
|
|
mock.call('/sys/block/sdb/device/wwid'),
|
|
mock.call('/sys/block/sdc/device/wwid')])
|
|
|
|
@mock.patch('six.moves.builtins.open', side_effect=IOError)
|
|
def test_get_sysfs_wwid_not_found(self, open_mock):
|
|
res = self.linuxscsi.get_sysfs_wwid(['sda', 'sdb'])
|
|
self.assertEqual('', res)
|
|
open_mock.assert_has_calls([mock.call('/sys/block/sda/device/wwid'),
|
|
mock.call('/sys/block/sdb/device/wwid')])
|
|
|
|
@mock.patch.object(linuxscsi.priv_rootwrap, 'unlink_root')
|
|
@mock.patch('glob.glob')
|
|
@mock.patch('os.path.realpath', side_effect=['/dev/sda', '/dev/sdb',
|
|
'/dev/sdc'])
|
|
def test_remove_scsi_symlinks(self, realpath_mock, glob_mock, unlink_mock):
|
|
paths = ['/dev/disk/by-id/scsi-wwid1', '/dev/disk/by-id/scsi-wwid2',
|
|
'/dev/disk/by-id/scsi-wwid3']
|
|
glob_mock.return_value = paths
|
|
self.linuxscsi._remove_scsi_symlinks(['sdb', 'sdc', 'sdd'])
|
|
glob_mock.assert_called_once_with('/dev/disk/by-id/scsi-*')
|
|
realpath_mock.assert_has_calls([mock.call(g) for g in paths])
|
|
unlink_mock.assert_called_once_with(no_errors=True, *paths[1:])
|
|
|
|
@mock.patch.object(linuxscsi.priv_rootwrap, 'unlink_root')
|
|
@mock.patch('glob.glob')
|
|
@mock.patch('os.path.realpath', side_effect=['/dev/sda', '/dev/sdb'])
|
|
def test_remove_scsi_symlinks_no_links(self, realpath_mock, glob_mock,
|
|
unlink_mock):
|
|
paths = ['/dev/disk/by-id/scsi-wwid1', '/dev/disk/by-id/scsi-wwid2']
|
|
glob_mock.return_value = paths
|
|
self.linuxscsi._remove_scsi_symlinks(['sdd', 'sde'])
|
|
glob_mock.assert_called_once_with('/dev/disk/by-id/scsi-*')
|
|
realpath_mock.assert_has_calls([mock.call(g) for g in paths])
|
|
unlink_mock.assert_not_called()
|
|
|
|
@mock.patch.object(linuxscsi.priv_rootwrap, 'unlink_root')
|
|
@mock.patch('glob.glob')
|
|
@mock.patch('os.path.realpath', side_effect=[OSError, '/dev/sda'])
|
|
def test_remove_scsi_symlinks_race_condition(self, realpath_mock,
|
|
glob_mock, unlink_mock):
|
|
paths = ['/dev/disk/by-id/scsi-wwid1', '/dev/disk/by-id/scsi-wwid2']
|
|
glob_mock.return_value = paths
|
|
self.linuxscsi._remove_scsi_symlinks(['sda'])
|
|
glob_mock.assert_called_once_with('/dev/disk/by-id/scsi-*')
|
|
realpath_mock.assert_has_calls([mock.call(g) for g in paths])
|
|
unlink_mock.assert_called_once_with(paths[1], no_errors=True)
|
|
|
|
@mock.patch('glob.glob')
|
|
def test_get_hctl_with_target(self, glob_mock):
|
|
glob_mock.return_value = [
|
|
'/sys/class/iscsi_host/host3/device/session1/target3:4:5',
|
|
'/sys/class/iscsi_host/host3/device/session1/target3:4:6']
|
|
res = self.linuxscsi.get_hctl('1', '2')
|
|
self.assertEqual(('3', '4', '5', '2'), res)
|
|
glob_mock.assert_called_once_with(
|
|
'/sys/class/iscsi_host/host*/device/session1/target*')
|
|
|
|
@mock.patch('glob.glob')
|
|
def test_get_hctl_no_target(self, glob_mock):
|
|
glob_mock.side_effect = [
|
|
[],
|
|
['/sys/class/iscsi_host/host3/device/session1',
|
|
'/sys/class/iscsi_host/host3/device/session1']]
|
|
res = self.linuxscsi.get_hctl('1', '2')
|
|
self.assertEqual(('3', '-', '-', '2'), res)
|
|
glob_mock.assert_has_calls(
|
|
[mock.call('/sys/class/iscsi_host/host*/device/session1/target*'),
|
|
mock.call('/sys/class/iscsi_host/host*/device/session1')])
|
|
|
|
@mock.patch('glob.glob', return_value=[])
|
|
def test_get_hctl_no_paths(self, glob_mock):
|
|
res = self.linuxscsi.get_hctl('1', '2')
|
|
self.assertIsNone(res)
|
|
glob_mock.assert_has_calls(
|
|
[mock.call('/sys/class/iscsi_host/host*/device/session1/target*'),
|
|
mock.call('/sys/class/iscsi_host/host*/device/session1')])
|
|
|
|
@mock.patch('glob.glob')
|
|
def test_device_name_by_hctl(self, glob_mock):
|
|
glob_mock.return_value = [
|
|
'/sys/class/scsi_host/host3/device/session1/target3:4:5/3:4:5:2/'
|
|
'block/sda2',
|
|
'/sys/class/scsi_host/host3/device/session1/target3:4:5/3:4:5:2/'
|
|
'block/sda']
|
|
res = self.linuxscsi.device_name_by_hctl('1', ('3', '4', '5', '2'))
|
|
self.assertEqual('sda', res)
|
|
glob_mock.assert_called_once_with(
|
|
'/sys/class/scsi_host/host3/device/session1/target3:4:5/3:4:5:2/'
|
|
'block/*')
|
|
|
|
@mock.patch('glob.glob')
|
|
def test_device_name_by_hctl_wildcards(self, glob_mock):
|
|
glob_mock.return_value = [
|
|
'/sys/class/scsi_host/host3/device/session1/target3:4:5/3:4:5:2/'
|
|
'block/sda2',
|
|
'/sys/class/scsi_host/host3/device/session1/target3:4:5/3:4:5:2/'
|
|
'block/sda']
|
|
res = self.linuxscsi.device_name_by_hctl('1', ('3', '-', '-', '2'))
|
|
self.assertEqual('sda', res)
|
|
glob_mock.assert_called_once_with(
|
|
'/sys/class/scsi_host/host3/device/session1/target3:*:*/3:*:*:2/'
|
|
'block/*')
|
|
|
|
@mock.patch('glob.glob', mock.Mock(return_value=[]))
|
|
def test_device_name_by_hctl_no_devices(self):
|
|
res = self.linuxscsi.device_name_by_hctl('1', ('4', '5', '6', '2'))
|
|
self.assertIsNone(res)
|
|
|
|
@mock.patch.object(linuxscsi.LinuxSCSI, 'echo_scsi_command')
|
|
def test_scsi_iscsi(self, echo_mock):
|
|
self.linuxscsi.scan_iscsi('host', 'channel', 'target', 'lun')
|
|
echo_mock.assert_called_once_with('/sys/class/scsi_host/hosthost/scan',
|
|
'channel target lun')
|
|
|
|
def test_multipath_add_wwid(self):
|
|
self.linuxscsi.multipath_add_wwid('wwid1')
|
|
self.assertEqual(['multipath -a wwid1'], self.cmds)
|
|
|
|
def test_multipath_add_path(self):
|
|
self.linuxscsi.multipath_add_path('/dev/sda')
|
|
self.assertEqual(['multipathd add path /dev/sda'], self.cmds)
|
|
|
|
@ddt.data({'con_props': {}, 'dev_info': {'path': mock.sentinel.path}},
|
|
{'con_props': None, 'dev_info': {'path': mock.sentinel.path}},
|
|
{'con_props': {'device_path': mock.sentinel.device_path},
|
|
'dev_info': {'path': mock.sentinel.path}})
|
|
@ddt.unpack
|
|
def test_get_dev_path_device_info(self, con_props, dev_info):
|
|
self.assertEqual(mock.sentinel.path,
|
|
self.linuxscsi.get_dev_path(con_props, dev_info))
|
|
|
|
@ddt.data({'con_props': {'device_path': mock.sentinel.device_path},
|
|
'dev_info': {'path': None}},
|
|
{'con_props': {'device_path': mock.sentinel.device_path},
|
|
'dev_info': {'path': ''}},
|
|
{'con_props': {'device_path': mock.sentinel.device_path},
|
|
'dev_info': {}},
|
|
{'con_props': {'device_path': mock.sentinel.device_path},
|
|
'dev_info': None})
|
|
@ddt.unpack
|
|
def test_get_dev_path_conn_props(self, con_props, dev_info):
|
|
self.assertEqual(mock.sentinel.device_path,
|
|
self.linuxscsi.get_dev_path(con_props, dev_info))
|
|
|
|
@ddt.data({'con_props': {'device_path': ''}, 'dev_info': {'path': None}},
|
|
{'con_props': {'device_path': None}, 'dev_info': {'path': ''}},
|
|
{'con_props': {}, 'dev_info': {}},
|
|
{'con_props': {}, 'dev_info': None})
|
|
@ddt.unpack
|
|
def test_get_dev_path_no_path(self, con_props, dev_info):
|
|
self.assertEqual('', self.linuxscsi.get_dev_path(con_props, dev_info))
|
|
|
|
@ddt.data(('/dev/sda', '/dev/sda', False, True, None),
|
|
# This checks that we ignore the was_multipath parameter if it
|
|
# doesn't make sense (because the used path is the one we are
|
|
# asking about)
|
|
('/dev/sda', '/dev/sda', True, True, None),
|
|
('/dev/sda', '', True, False, None),
|
|
# Check for encrypted volume
|
|
('/dev/link_sda', '/dev/disk/by-path/pci-XYZ', False, True,
|
|
('/dev/sda', '/dev/mapper/crypt-pci-XYZ')),
|
|
('/dev/link_sda', '/dev/link_sdb', False, False, ('/dev/sda',
|
|
'/dev/sdb')),
|
|
('/dev/link_sda', '/dev/link2_sda', False, True, ('/dev/sda',
|
|
'/dev/sda')))
|
|
@ddt.unpack
|
|
def test_requires_flush(self, path, path_used, was_multipath, expected,
|
|
real_paths):
|
|
with mock.patch('os.path.realpath', side_effect=real_paths) as mocked:
|
|
self.assertEqual(
|
|
expected,
|
|
self.linuxscsi.requires_flush(path, path_used, was_multipath))
|
|
if real_paths:
|
|
mocked.assert_has_calls([mock.call(path),
|
|
mock.call(path_used)])
|