1# (c) Copyright 2013 Hewlett-Packard Development Company, L.P. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); you may 4# not use this file except in compliance with the License. You may obtain 5# a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12# License for the specific language governing permissions and limitations 13# under the License. 14 15import os 16import os.path 17import textwrap 18from unittest import mock 19 20import ddt 21from oslo_concurrency import processutils as putils 22from oslo_log import log as logging 23 24from os_brick import exception 25from os_brick.initiator import linuxscsi 26from os_brick.tests import base 27 28LOG = logging.getLogger(__name__) 29 30 31@ddt.ddt 32class LinuxSCSITestCase(base.TestCase): 33 def setUp(self): 34 super(LinuxSCSITestCase, self).setUp() 35 self.cmds = [] 36 self.realpath = os.path.realpath 37 self.mock_object(os.path, 'realpath', return_value='/dev/sdc') 38 self.mock_object(os, 'stat', returns=os.stat(__file__)) 39 self.linuxscsi = linuxscsi.LinuxSCSI(None, execute=self.fake_execute) 40 41 def fake_execute(self, *cmd, **kwargs): 42 self.cmds.append(" ".join(cmd)) 43 return "", None 44 45 def test_echo_scsi_command(self): 46 self.linuxscsi.echo_scsi_command("/some/path", "1") 47 expected_commands = ['tee -a /some/path'] 48 self.assertEqual(expected_commands, self.cmds) 49 50 @mock.patch.object(os.path, 'realpath') 51 def test_get_name_from_path(self, realpath_mock): 52 device_name = "/dev/sdc" 53 realpath_mock.return_value = device_name 54 disk_path = ("/dev/disk/by-path/ip-10.10.220.253:3260-" 55 "iscsi-iqn.2000-05.com.3pardata:21810002ac00383d-lun-0") 56 name = self.linuxscsi.get_name_from_path(disk_path) 57 self.assertEqual(device_name, name) 58 disk_path = ("/dev/disk/by-path/pci-0000:00:00.0-ip-10.9.8.7:3260-" 59 "iscsi-iqn.2000-05.com.openstack:2180002ac00383d-lun-0") 60 name = self.linuxscsi.get_name_from_path(disk_path) 61 self.assertEqual(device_name, name) 62 realpath_mock.return_value = "bogus" 63 name = self.linuxscsi.get_name_from_path(disk_path) 64 self.assertIsNone(name) 65 66 @mock.patch.object(os.path, 'exists', return_value=False) 67 def test_remove_scsi_device(self, exists_mock): 68 self.linuxscsi.remove_scsi_device("/dev/sdc") 69 expected_commands = [] 70 self.assertEqual(expected_commands, self.cmds) 71 exists_mock.return_value = True 72 self.linuxscsi.remove_scsi_device("/dev/sdc") 73 expected_commands = [ 74 ('blockdev --flushbufs /dev/sdc'), 75 ('tee -a /sys/block/sdc/device/delete')] 76 self.assertEqual(expected_commands, self.cmds) 77 78 @mock.patch.object(linuxscsi.LinuxSCSI, 'echo_scsi_command') 79 @mock.patch.object(linuxscsi.LinuxSCSI, 'flush_device_io') 80 @mock.patch.object(os.path, 'exists', return_value=True) 81 def test_remove_scsi_device_force(self, exists_mock, flush_mock, 82 echo_mock): 83 """With force we'll always call delete even if flush fails.""" 84 exc = exception.ExceptionChainer() 85 flush_mock.side_effect = Exception() 86 echo_mock.side_effect = Exception() 87 device = '/dev/sdc' 88 89 self.linuxscsi.remove_scsi_device(device, force=True, exc=exc) 90 # The context manager has caught the exceptions 91 self.assertTrue(exc) 92 flush_mock.assert_called_once_with(device) 93 echo_mock.assert_called_once_with('/sys/block/sdc/device/delete', '1') 94 95 @mock.patch.object(os.path, 'exists', return_value=False) 96 def test_remove_scsi_device_no_flush(self, exists_mock): 97 self.linuxscsi.remove_scsi_device("/dev/sdc") 98 expected_commands = [] 99 self.assertEqual(expected_commands, self.cmds) 100 exists_mock.return_value = True 101 self.linuxscsi.remove_scsi_device("/dev/sdc", flush=False) 102 expected_commands = [('tee -a /sys/block/sdc/device/delete')] 103 self.assertEqual(expected_commands, self.cmds) 104 105 @mock.patch('os_brick.utils._time_sleep') 106 @mock.patch('os.path.exists', return_value=True) 107 def test_wait_for_volumes_removal_failure(self, exists_mock, sleep_mock): 108 retries = 61 109 names = ('sda', 'sdb') 110 self.assertRaises(exception.VolumePathNotRemoved, 111 self.linuxscsi.wait_for_volumes_removal, names) 112 exists_mock.assert_has_calls([mock.call('/dev/' + name) 113 for name in names] * retries) 114 self.assertEqual(retries - 1, sleep_mock.call_count) 115 116 @mock.patch('os_brick.utils._time_sleep') 117 @mock.patch('os.path.exists', side_effect=(True, True, False, False)) 118 def test_wait_for_volumes_removal_retry(self, exists_mock, sleep_mock): 119 names = ('sda', 'sdb') 120 self.linuxscsi.wait_for_volumes_removal(names) 121 exists_mock.assert_has_calls([mock.call('/dev/' + name) 122 for name in names] * 2) 123 self.assertEqual(1, sleep_mock.call_count) 124 125 def test_flush_multipath_device(self): 126 dm_map_name = '3600d0230000000000e13955cc3757800' 127 with mock.patch.object(self.linuxscsi, '_execute') as exec_mock: 128 self.linuxscsi.flush_multipath_device(dm_map_name) 129 130 exec_mock.assert_called_once_with( 131 'multipath', '-f', dm_map_name, run_as_root=True, attempts=3, 132 timeout=300, interval=10, root_helper=self.linuxscsi._root_helper) 133 134 def test_get_scsi_wwn(self): 135 fake_path = '/dev/disk/by-id/somepath' 136 fake_wwn = '1234567890' 137 138 def fake_execute(*cmd, **kwargs): 139 return fake_wwn, None 140 141 self.linuxscsi._execute = fake_execute 142 wwn = self.linuxscsi.get_scsi_wwn(fake_path) 143 self.assertEqual(fake_wwn, wwn) 144 145 @mock.patch('builtins.open') 146 def test_get_dm_name(self, open_mock): 147 dm_map_name = '3600d0230000000000e13955cc3757800' 148 cm_open = open_mock.return_value.__enter__.return_value 149 cm_open.read.return_value = dm_map_name 150 res = self.linuxscsi.get_dm_name('dm-0') 151 self.assertEqual(dm_map_name, res) 152 open_mock.assert_called_once_with('/sys/block/dm-0/dm/name') 153 154 @mock.patch('builtins.open', side_effect=IOError) 155 def test_get_dm_name_failure(self, open_mock): 156 self.assertEqual('', self.linuxscsi.get_dm_name('dm-0')) 157 158 @mock.patch('glob.glob', side_effect=[[], ['/sys/block/sda/holders/dm-9']]) 159 def test_find_sysfs_multipath_dm(self, glob_mock): 160 device_names = ('sda', 'sdb') 161 res = self.linuxscsi.find_sysfs_multipath_dm(device_names) 162 self.assertEqual('dm-9', res) 163 glob_mock.assert_has_calls([mock.call('/sys/block/sda/holders/dm-*'), 164 mock.call('/sys/block/sdb/holders/dm-*')]) 165 166 @mock.patch('glob.glob', return_value=[]) 167 def test_find_sysfs_multipath_dm_not_found(self, glob_mock): 168 device_names = ('sda', 'sdb') 169 res = self.linuxscsi.find_sysfs_multipath_dm(device_names) 170 self.assertIsNone(res) 171 glob_mock.assert_has_calls([mock.call('/sys/block/sda/holders/dm-*'), 172 mock.call('/sys/block/sdb/holders/dm-*')]) 173 174 @mock.patch.object(linuxscsi.LinuxSCSI, '_execute') 175 @mock.patch('os.path.exists', return_value=True) 176 def test_flush_device_io(self, exists_mock, exec_mock): 177 device = '/dev/sda' 178 self.linuxscsi.flush_device_io(device) 179 exists_mock.assert_called_once_with(device) 180 exec_mock.assert_called_once_with( 181 'blockdev', '--flushbufs', device, run_as_root=True, attempts=3, 182 timeout=300, interval=10, root_helper=self.linuxscsi._root_helper) 183 184 @mock.patch('os.path.exists', return_value=False) 185 def test_flush_device_io_non_existent(self, exists_mock): 186 device = '/dev/sda' 187 self.linuxscsi.flush_device_io(device) 188 exists_mock.assert_called_once_with(device) 189 190 @mock.patch.object(os.path, 'exists', return_value=True) 191 def test_find_multipath_device_path(self, exists_mock): 192 fake_wwn = '1234567890' 193 found_path = self.linuxscsi.find_multipath_device_path(fake_wwn) 194 expected_path = '/dev/disk/by-id/dm-uuid-mpath-%s' % fake_wwn 195 self.assertEqual(expected_path, found_path) 196 197 @mock.patch('os_brick.utils._time_sleep') 198 @mock.patch.object(os.path, 'exists') 199 def test_find_multipath_device_path_mapper(self, exists_mock, sleep_mock): 200 # the wait loop tries 3 times before it gives up 201 # we want to test failing to find the 202 # /dev/disk/by-id/dm-uuid-mpath-<WWN> path 203 # but finding the 204 # /dev/mapper/<WWN> path 205 exists_mock.side_effect = [False, False, False, True] 206 fake_wwn = '1234567890' 207 found_path = self.linuxscsi.find_multipath_device_path(fake_wwn) 208 expected_path = '/dev/mapper/%s' % fake_wwn 209 self.assertEqual(expected_path, found_path) 210 self.assertTrue(sleep_mock.called) 211 212 @mock.patch.object(os.path, 'exists', return_value=False) 213 @mock.patch('os_brick.utils._time_sleep') 214 def test_find_multipath_device_path_fail(self, exists_mock, sleep_mock): 215 fake_wwn = '1234567890' 216 found_path = self.linuxscsi.find_multipath_device_path(fake_wwn) 217 self.assertIsNone(found_path) 218 219 @mock.patch.object(os.path, 'exists', return_value=False) 220 @mock.patch('os_brick.utils._time_sleep') 221 def test_wait_for_path_not_found(self, exists_mock, sleep_mock): 222 path = "/dev/disk/by-id/dm-uuid-mpath-%s" % '1234567890' 223 self.assertRaisesRegex(exception.VolumeDeviceNotFound, 224 r'Volume device not found at %s' % path, 225 self.linuxscsi.wait_for_path, 226 path) 227 228 @ddt.data({'do_raise': False, 'force': False}, 229 {'do_raise': True, 'force': True}) 230 @ddt.unpack 231 @mock.patch.object(linuxscsi.LinuxSCSI, '_remove_scsi_symlinks') 232 @mock.patch.object(linuxscsi.LinuxSCSI, 'multipath_del_path') 233 @mock.patch.object(linuxscsi.LinuxSCSI, 'is_multipath_running', 234 return_value=True) 235 @mock.patch.object(linuxscsi.LinuxSCSI, 'flush_multipath_device') 236 @mock.patch.object(linuxscsi.LinuxSCSI, 'get_dm_name') 237 @mock.patch.object(linuxscsi.LinuxSCSI, 'find_sysfs_multipath_dm') 238 @mock.patch.object(linuxscsi.LinuxSCSI, 'wait_for_volumes_removal') 239 @mock.patch.object(linuxscsi.LinuxSCSI, 'remove_scsi_device') 240 def test_remove_connection_multipath_complete(self, remove_mock, wait_mock, 241 find_dm_mock, 242 get_dm_name_mock, 243 flush_mp_mock, 244 is_mp_running_mock, 245 mp_del_path_mock, 246 remove_link_mock, 247 do_raise, force): 248 if do_raise: 249 flush_mp_mock.side_effect = Exception 250 devices_names = ('sda', 'sdb') 251 exc = exception.ExceptionChainer() 252 mp_name = self.linuxscsi.remove_connection(devices_names, 253 force=mock.sentinel.Force, 254 exc=exc) 255 find_dm_mock.assert_called_once_with(devices_names) 256 get_dm_name_mock.assert_called_once_with(find_dm_mock.return_value) 257 flush_mp_mock.assert_called_once_with(get_dm_name_mock.return_value) 258 self.assertEqual(get_dm_name_mock.return_value if do_raise else None, 259 mp_name) 260 is_mp_running_mock.assert_not_called() 261 mp_del_path_mock.assert_has_calls([ 262 mock.call('/dev/sda'), mock.call('/dev/sdb')]) 263 remove_mock.assert_has_calls([ 264 mock.call('/dev/sda', mock.sentinel.Force, exc, False), 265 mock.call('/dev/sdb', mock.sentinel.Force, exc, False)]) 266 wait_mock.assert_called_once_with(devices_names) 267 self.assertEqual(do_raise, bool(exc)) 268 remove_link_mock.assert_called_once_with(devices_names) 269 270 @mock.patch.object(linuxscsi.LinuxSCSI, '_remove_scsi_symlinks') 271 @mock.patch.object(linuxscsi.LinuxSCSI, 'multipath_del_path') 272 @mock.patch.object(linuxscsi.LinuxSCSI, 'is_multipath_running', 273 return_value=True) 274 @mock.patch.object(linuxscsi.LinuxSCSI, 'flush_multipath_device') 275 @mock.patch.object(linuxscsi.LinuxSCSI, 'get_dm_name') 276 @mock.patch.object(linuxscsi.LinuxSCSI, 'find_sysfs_multipath_dm', 277 return_value=None) 278 @mock.patch.object(linuxscsi.LinuxSCSI, 'wait_for_volumes_removal') 279 @mock.patch.object(linuxscsi.LinuxSCSI, 'remove_scsi_device') 280 def test_remove_connection_multipath_complete_no_dm(self, remove_mock, 281 wait_mock, 282 find_dm_mock, 283 get_dm_name_mock, 284 flush_mp_mock, 285 is_mp_running_mock, 286 mp_del_path_mock, 287 remove_link_mock): 288 devices_names = ('sda', 'sdb') 289 exc = exception.ExceptionChainer() 290 mp_name = self.linuxscsi.remove_connection(devices_names, 291 force=mock.sentinel.Force, 292 exc=exc) 293 find_dm_mock.assert_called_once_with(devices_names) 294 get_dm_name_mock.assert_not_called() 295 flush_mp_mock.assert_not_called() 296 self.assertIsNone(mp_name) 297 is_mp_running_mock.assert_called_once() 298 mp_del_path_mock.assert_has_calls([ 299 mock.call('/dev/sda'), mock.call('/dev/sdb')]) 300 remove_mock.assert_has_calls([ 301 mock.call('/dev/sda', mock.sentinel.Force, exc, False), 302 mock.call('/dev/sdb', mock.sentinel.Force, exc, False)]) 303 wait_mock.assert_called_once_with(devices_names) 304 self.assertFalse(bool(exc)) 305 remove_link_mock.assert_called_once_with(devices_names) 306 307 @mock.patch.object(linuxscsi.LinuxSCSI, '_remove_scsi_symlinks') 308 @mock.patch.object(linuxscsi.LinuxSCSI, 'multipath_del_path') 309 @mock.patch.object(linuxscsi.LinuxSCSI, 'is_multipath_running', 310 return_value=True) 311 @mock.patch.object(linuxscsi.LinuxSCSI, 'flush_multipath_device', 312 side_effect=Exception) 313 @mock.patch.object(linuxscsi.LinuxSCSI, 'get_dm_name') 314 @mock.patch.object(linuxscsi.LinuxSCSI, 'find_sysfs_multipath_dm') 315 @mock.patch.object(linuxscsi.LinuxSCSI, 'wait_for_volumes_removal') 316 @mock.patch.object(linuxscsi.LinuxSCSI, 'remove_scsi_device') 317 def test_remove_connection_multipath_fail(self, remove_mock, wait_mock, 318 find_dm_mock, get_dm_name_mock, 319 flush_mp_mock, 320 is_mp_running_mock, 321 mp_del_path_mock, 322 remove_link_mock): 323 flush_mp_mock.side_effect = exception.ExceptionChainer 324 devices_names = ('sda', 'sdb') 325 exc = exception.ExceptionChainer() 326 self.assertRaises(exception.ExceptionChainer, 327 self.linuxscsi.remove_connection, 328 devices_names, force=False, exc=exc) 329 find_dm_mock.assert_called_once_with(devices_names) 330 get_dm_name_mock.assert_called_once_with(find_dm_mock.return_value) 331 flush_mp_mock.assert_called_once_with(get_dm_name_mock.return_value) 332 is_mp_running_mock.assert_not_called() 333 mp_del_path_mock.assert_not_called() 334 remove_mock.assert_not_called() 335 wait_mock.assert_not_called() 336 remove_link_mock.assert_not_called() 337 self.assertTrue(bool(exc)) 338 339 @mock.patch.object(linuxscsi.LinuxSCSI, '_remove_scsi_symlinks') 340 @mock.patch.object(linuxscsi.LinuxSCSI, 'multipath_del_path') 341 @mock.patch.object(linuxscsi.LinuxSCSI, 'is_multipath_running', 342 return_value=True) 343 @mock.patch.object(linuxscsi.LinuxSCSI, 'find_sysfs_multipath_dm') 344 @mock.patch.object(linuxscsi.LinuxSCSI, 'wait_for_volumes_removal') 345 @mock.patch.object(linuxscsi.LinuxSCSI, 'remove_scsi_device') 346 def test_remove_connection_singlepath_no_path(self, remove_mock, wait_mock, 347 find_dm_mock, 348 is_mp_running_mock, 349 mp_del_path_mock, 350 remove_link_mock): 351 # Test remove connection when we didn't form a multipath and didn't 352 # even use any of the devices that were found. This means that we 353 # don't flush any of the single paths when removing them. 354 find_dm_mock.return_value = None 355 devices_names = ('sda', 'sdb') 356 exc = exception.ExceptionChainer() 357 self.linuxscsi.remove_connection(devices_names, 358 force=mock.sentinel.Force, 359 exc=exc) 360 find_dm_mock.assert_called_once_with(devices_names) 361 is_mp_running_mock.assert_called_once() 362 mp_del_path_mock.assert_has_calls([ 363 mock.call('/dev/sda'), mock.call('/dev/sdb')]) 364 remove_mock.assert_has_calls( 365 [mock.call('/dev/sda', mock.sentinel.Force, exc, False), 366 mock.call('/dev/sdb', mock.sentinel.Force, exc, False)]) 367 wait_mock.assert_called_once_with(devices_names) 368 remove_link_mock.assert_called_once_with(devices_names) 369 370 @mock.patch.object(linuxscsi.LinuxSCSI, '_remove_scsi_symlinks') 371 @mock.patch.object(linuxscsi.LinuxSCSI, 'multipath_del_path') 372 @mock.patch.object(linuxscsi.LinuxSCSI, 'is_multipath_running', 373 return_value=False) 374 @mock.patch.object(linuxscsi.LinuxSCSI, 'find_sysfs_multipath_dm') 375 @mock.patch.object(linuxscsi.LinuxSCSI, 'wait_for_volumes_removal') 376 @mock.patch.object(linuxscsi.LinuxSCSI, 'remove_scsi_device') 377 def test_remove_connection_singlepath_used(self, remove_mock, wait_mock, 378 find_dm_mock, 379 is_mp_running_mock, 380 mp_del_path_mock, 381 remove_link_mock): 382 # Test remove connection when we didn't form a multipath and just used 383 # one of the single paths that were found. This means that we don't 384 # flush any of the single paths when removing them. 385 find_dm_mock.return_value = None 386 devices_names = ('sda', 'sdb') 387 exc = exception.ExceptionChainer() 388 389 # realpath was mocked on test setup 390 with mock.patch('os.path.realpath', side_effect=self.realpath): 391 self.linuxscsi.remove_connection(devices_names, 392 force=mock.sentinel.Force, 393 exc=exc, path_used='/dev/sdb', 394 was_multipath=False) 395 find_dm_mock.assert_called_once_with(devices_names) 396 is_mp_running_mock.assert_called_once() 397 mp_del_path_mock.assert_not_called() 398 remove_mock.assert_has_calls( 399 [mock.call('/dev/sda', mock.sentinel.Force, exc, False), 400 mock.call('/dev/sdb', mock.sentinel.Force, exc, True)]) 401 wait_mock.assert_called_once_with(devices_names) 402 remove_link_mock.assert_called_once_with(devices_names) 403 404 def test_find_multipath_device_3par_ufn(self): 405 def fake_execute(*cmd, **kwargs): 406 out = ("mpath6 (350002ac20398383d) dm-3 3PARdata,VV\n" 407 "size=2.0G features='0' hwhandler='0' wp=rw\n" 408 "`-+- policy='round-robin 0' prio=-1 status=active\n" 409 " |- 0:0:0:1 sde 8:64 active undef running\n" 410 " `- 2:0:0:1 sdf 8:80 active undef running\n" 411 ) 412 return out, None 413 414 self.linuxscsi._execute = fake_execute 415 416 info = self.linuxscsi.find_multipath_device('/dev/sde') 417 418 self.assertEqual("350002ac20398383d", info["id"]) 419 self.assertEqual("mpath6", info["name"]) 420 self.assertEqual("/dev/mapper/mpath6", info["device"]) 421 422 self.assertEqual("/dev/sde", info['devices'][0]['device']) 423 self.assertEqual("0", info['devices'][0]['host']) 424 self.assertEqual("0", info['devices'][0]['id']) 425 self.assertEqual("0", info['devices'][0]['channel']) 426 self.assertEqual("1", info['devices'][0]['lun']) 427 428 self.assertEqual("/dev/sdf", info['devices'][1]['device']) 429 self.assertEqual("2", info['devices'][1]['host']) 430 self.assertEqual("0", info['devices'][1]['id']) 431 self.assertEqual("0", info['devices'][1]['channel']) 432 self.assertEqual("1", info['devices'][1]['lun']) 433 434 def test_find_multipath_device_svc(self): 435 def fake_execute(*cmd, **kwargs): 436 out = ("36005076da00638089c000000000004d5 dm-2 IBM,2145\n" 437 "size=954M features='1 queue_if_no_path' hwhandler='0'" 438 " wp=rw\n" 439 "|-+- policy='round-robin 0' prio=-1 status=active\n" 440 "| |- 6:0:2:0 sde 8:64 active undef running\n" 441 "| `- 6:0:4:0 sdg 8:96 active undef running\n" 442 "`-+- policy='round-robin 0' prio=-1 status=enabled\n" 443 " |- 6:0:3:0 sdf 8:80 active undef running\n" 444 " `- 6:0:5:0 sdh 8:112 active undef running\n" 445 ) 446 return out, None 447 448 self.linuxscsi._execute = fake_execute 449 450 info = self.linuxscsi.find_multipath_device('/dev/sde') 451 452 self.assertEqual("36005076da00638089c000000000004d5", info["id"]) 453 self.assertEqual("36005076da00638089c000000000004d5", info["name"]) 454 self.assertEqual("/dev/mapper/36005076da00638089c000000000004d5", 455 info["device"]) 456 457 self.assertEqual("/dev/sde", info['devices'][0]['device']) 458 self.assertEqual("6", info['devices'][0]['host']) 459 self.assertEqual("0", info['devices'][0]['channel']) 460 self.assertEqual("2", info['devices'][0]['id']) 461 self.assertEqual("0", info['devices'][0]['lun']) 462 463 self.assertEqual("/dev/sdf", info['devices'][2]['device']) 464 self.assertEqual("6", info['devices'][2]['host']) 465 self.assertEqual("0", info['devices'][2]['channel']) 466 self.assertEqual("3", info['devices'][2]['id']) 467 self.assertEqual("0", info['devices'][2]['lun']) 468 469 def test_find_multipath_device_ds8000(self): 470 def fake_execute(*cmd, **kwargs): 471 out = ("36005076303ffc48e0000000000000101 dm-2 IBM,2107900\n" 472 "size=1.0G features='1 queue_if_no_path' hwhandler='0'" 473 " wp=rw\n" 474 "`-+- policy='round-robin 0' prio=-1 status=active\n" 475 " |- 6:0:2:0 sdd 8:64 active undef running\n" 476 " `- 6:1:0:3 sdc 8:32 active undef running\n" 477 ) 478 return out, None 479 480 self.linuxscsi._execute = fake_execute 481 482 info = self.linuxscsi.find_multipath_device('/dev/sdd') 483 484 self.assertEqual("36005076303ffc48e0000000000000101", info["id"]) 485 self.assertEqual("36005076303ffc48e0000000000000101", info["name"]) 486 self.assertEqual("/dev/mapper/36005076303ffc48e0000000000000101", 487 info["device"]) 488 489 self.assertEqual("/dev/sdd", info['devices'][0]['device']) 490 self.assertEqual("6", info['devices'][0]['host']) 491 self.assertEqual("0", info['devices'][0]['channel']) 492 self.assertEqual("2", info['devices'][0]['id']) 493 self.assertEqual("0", info['devices'][0]['lun']) 494 495 self.assertEqual("/dev/sdc", info['devices'][1]['device']) 496 self.assertEqual("6", info['devices'][1]['host']) 497 self.assertEqual("1", info['devices'][1]['channel']) 498 self.assertEqual("0", info['devices'][1]['id']) 499 self.assertEqual("3", info['devices'][1]['lun']) 500 501 def test_find_multipath_device_with_error(self): 502 def fake_execute(*cmd, **kwargs): 503 out = ("Oct 13 10:24:01 | /lib/udev/scsi_id exited with 1\n" 504 "36005076303ffc48e0000000000000101 dm-2 IBM,2107900\n" 505 "size=1.0G features='1 queue_if_no_path' hwhandler='0'" 506 " wp=rw\n" 507 "`-+- policy='round-robin 0' prio=-1 status=active\n" 508 " |- 6:0:2:0 sdd 8:64 active undef running\n" 509 " `- 6:1:0:3 sdc 8:32 active undef running\n" 510 ) 511 return out, None 512 513 self.linuxscsi._execute = fake_execute 514 515 info = self.linuxscsi.find_multipath_device('/dev/sdd') 516 517 self.assertEqual("36005076303ffc48e0000000000000101", info["id"]) 518 self.assertEqual("36005076303ffc48e0000000000000101", info["name"]) 519 self.assertEqual("/dev/mapper/36005076303ffc48e0000000000000101", 520 info["device"]) 521 522 self.assertEqual("/dev/sdd", info['devices'][0]['device']) 523 self.assertEqual("6", info['devices'][0]['host']) 524 self.assertEqual("0", info['devices'][0]['channel']) 525 self.assertEqual("2", info['devices'][0]['id']) 526 self.assertEqual("0", info['devices'][0]['lun']) 527 528 self.assertEqual("/dev/sdc", info['devices'][1]['device']) 529 self.assertEqual("6", info['devices'][1]['host']) 530 self.assertEqual("1", info['devices'][1]['channel']) 531 self.assertEqual("0", info['devices'][1]['id']) 532 self.assertEqual("3", info['devices'][1]['lun']) 533 534 def test_find_multipath_device_with_multiple_errors(self): 535 def fake_execute(*cmd, **kwargs): 536 out = ("Jun 21 04:39:26 | 8:160: path wwid appears to have " 537 "changed. Using old wwid.\n\n" 538 "Jun 21 04:39:26 | 65:208: path wwid appears to have " 539 "changed. Using old wwid.\n\n" 540 "Jun 21 04:39:26 | 65:208: path wwid appears to have " 541 "changed. Using old wwid.\n" 542 "3624a93707edcfde1127040370004ee62 dm-84 PURE ," 543 "FlashArray\n" 544 "size=100G features='0' hwhandler='0' wp=rw\n" 545 "`-+- policy='queue-length 0' prio=1 status=active\n" 546 " |- 8:0:0:9 sdaa 65:160 active ready running\n" 547 " `- 8:0:1:9 sdac 65:192 active ready running\n" 548 ) 549 return out, None 550 551 self.linuxscsi._execute = fake_execute 552 553 info = self.linuxscsi.find_multipath_device('/dev/sdaa') 554 555 self.assertEqual("3624a93707edcfde1127040370004ee62", info["id"]) 556 self.assertEqual("3624a93707edcfde1127040370004ee62", info["name"]) 557 self.assertEqual("/dev/mapper/3624a93707edcfde1127040370004ee62", 558 info["device"]) 559 560 self.assertEqual("/dev/sdaa", info['devices'][0]['device']) 561 self.assertEqual("8", info['devices'][0]['host']) 562 self.assertEqual("0", info['devices'][0]['channel']) 563 self.assertEqual("0", info['devices'][0]['id']) 564 self.assertEqual("9", info['devices'][0]['lun']) 565 566 self.assertEqual("/dev/sdac", info['devices'][1]['device']) 567 self.assertEqual("8", info['devices'][1]['host']) 568 self.assertEqual("0", info['devices'][1]['channel']) 569 self.assertEqual("1", info['devices'][1]['id']) 570 self.assertEqual("9", info['devices'][1]['lun']) 571 572 @mock.patch('os_brick.utils._time_sleep') 573 def test_wait_for_rw(self, mock_sleep): 574 lsblk_output = """3624a93709a738ed78583fd1200143029 (dm-2) 0 575sdb 0 5763624a93709a738ed78583fd120014724e (dm-1) 0 577sdc 0 5783624a93709a738ed78583fd120014a2bb (dm-0) 0 579sdd 0 5803624a93709a738ed78583fd1200143029 (dm-2) 0 581sde 0 5823624a93709a738ed78583fd120014724e (dm-1) 0 583sdf 0 5843624a93709a738ed78583fd120014a2bb (dm-0) 0 585sdg 0 5863624a93709a738ed78583fd1200143029 (dm-2) 0 587sdh 0 5883624a93709a738ed78583fd120014724e (dm-1) 0 589sdi 0 5903624a93709a738ed78583fd120014a2bb (dm-0) 0 591sdj 0 5923624a93709a738ed78583fd1200143029 (dm-2) 0 593sdk 0 5943624a93709a738ed78583fd120014724e (dm-1) 0 595sdl 0 5963624a93709a738ed78583fd120014a2bb (dm-0) 0 597sdm 0 598vda1 0 599vdb 0 600vdb1 0 601loop0 0""" 602 603 mock_execute = mock.Mock() 604 mock_execute.return_value = (lsblk_output, None) 605 self.linuxscsi._execute = mock_execute 606 607 wwn = '3624a93709a738ed78583fd120014a2bb' 608 path = '/dev/disk/by-id/dm-uuid-mpath-' + wwn 609 610 # Ensure no exception is raised and no sleep is called 611 self.linuxscsi.wait_for_rw(wwn, path) 612 self.assertFalse(mock_sleep.called) 613 614 @mock.patch('os_brick.utils._time_sleep') 615 def test_wait_for_rw_needs_retry(self, mock_sleep): 616 lsblk_ro_output = """3624a93709a738ed78583fd1200143029 (dm-2) 0 617sdb 0 6183624a93709a738ed78583fd120014724e (dm-1) 0 619sdc 0 6203624a93709a738ed78583fd120014a2bb (dm-0) 0 621sdd 0 6223624a93709a738ed78583fd1200143029 (dm-2) 1 623sde 0 6243624a93709a738ed78583fd120014724e (dm-1) 0 625sdf 0 6263624a93709a738ed78583fd120014a2bb (dm-0) 0 627sdg 0 6283624a93709a738ed78583fd1200143029 (dm-2) 1 629sdh 0 6303624a93709a738ed78583fd120014724e (dm-1) 0 631sdi 0 6323624a93709a738ed78583fd120014a2bb (dm-0) 0 633sdj 0 6343624a93709a738ed78583fd1200143029 (dm-2) 1 635sdk 0 6363624a93709a738ed78583fd120014724e (dm-1) 0 637sdl 0 6383624a93709a738ed78583fd120014a2bb (dm-0) 0 639sdm 0 640vda1 0 641vdb 0 642vdb1 0 643loop0 0""" 644 lsblk_rw_output = """3624a93709a738ed78583fd1200143029 (dm-2) 0 645sdb 0 6463624a93709a738ed78583fd120014724e (dm-1) 0 647sdc 0 6483624a93709a738ed78583fd120014a2bb (dm-0) 0 649sdd 0 6503624a93709a738ed78583fd1200143029 (dm-2) 0 651sde 0 6523624a93709a738ed78583fd120014724e (dm-1) 0 653sdf 0 6543624a93709a738ed78583fd120014a2bb (dm-0) 0 655sdg 0 6563624a93709a738ed78583fd1200143029 (dm-2) 0 657sdh 0 6583624a93709a738ed78583fd120014724e (dm-1) 0 659sdi 0 6603624a93709a738ed78583fd120014a2bb (dm-0) 0 661sdj 0 6623624a93709a738ed78583fd1200143029 (dm-2) 0 663sdk 0 6643624a93709a738ed78583fd120014724e (dm-1) 0 665sdl 0 6663624a93709a738ed78583fd120014a2bb (dm-0) 0 667sdm 0 668vda1 0 669vdb 0 670vdb1 0 671loop0 0""" 672 mock_execute = mock.Mock() 673 mock_execute.side_effect = [(lsblk_ro_output, None), 674 ('', None), # multipath -r output 675 (lsblk_rw_output, None)] 676 self.linuxscsi._execute = mock_execute 677 678 wwn = '3624a93709a738ed78583fd1200143029' 679 path = '/dev/disk/by-id/dm-uuid-mpath-' + wwn 680 681 self.linuxscsi.wait_for_rw(wwn, path) 682 self.assertEqual(1, mock_sleep.call_count) 683 684 @mock.patch('os_brick.utils._time_sleep') 685 def test_wait_for_rw_always_readonly(self, mock_sleep): 686 lsblk_output = """3624a93709a738ed78583fd1200143029 (dm-2) 0 687sdb 0 6883624a93709a738ed78583fd120014724e (dm-1) 0 689sdc 0 6903624a93709a738ed78583fd120014a2bb (dm-0) 1 691sdd 0 6923624a93709a738ed78583fd1200143029 (dm-2) 0 693sde 0 6943624a93709a738ed78583fd120014724e (dm-1) 0 695sdf 0 6963624a93709a738ed78583fd120014a2bb (dm-0) 1 697sdg 0 6983624a93709a738ed78583fd1200143029 (dm-2) 0 699sdh 0 7003624a93709a738ed78583fd120014724e (dm-1) 0 701sdi 0 7023624a93709a738ed78583fd120014a2bb (dm-0) 1 703sdj 0 7043624a93709a738ed78583fd1200143029 (dm-2) 0 705sdk 0 7063624a93709a738ed78583fd120014724e (dm-1) 0 707sdl 0 7083624a93709a738ed78583fd120014a2bb (dm-0) 1 709sdm 0 710vda1 0 711vdb 0 712vdb1 0 713loop0 0""" 714 715 mock_execute = mock.Mock() 716 mock_execute.return_value = (lsblk_output, None) 717 self.linuxscsi._execute = mock_execute 718 719 wwn = '3624a93709a738ed78583fd120014a2bb' 720 path = '/dev/disk/by-id/dm-uuid-mpath-' + wwn 721 722 self.assertRaises(exception.BlockDeviceReadOnly, 723 self.linuxscsi.wait_for_rw, 724 wwn, 725 path) 726 727 self.assertEqual(4, mock_sleep.call_count) 728 729 def test_find_multipath_device_with_action(self): 730 def fake_execute(*cmd, **kwargs): 731 out = textwrap.dedent(""" 732 create: 36005076303ffc48e0000000000000101 dm-2 IBM,2107900 733 size=1.0G features='1 queue_if_no_path' hwhandler='0' 734 wp=rw 735 `-+- policy='round-robin 0' prio=-1 status=active 736 |- 6:0:2:0 sdd 8:64 active undef running 737 `- 6:1:0:3 sdc 8:32 active undef running 738 """) 739 return out, None 740 741 self.linuxscsi._execute = fake_execute 742 info = self.linuxscsi.find_multipath_device('/dev/sdd') 743 LOG.error("Device info: %s", info) 744 745 self.assertEqual('36005076303ffc48e0000000000000101', info['id']) 746 self.assertEqual('36005076303ffc48e0000000000000101', info['name']) 747 self.assertEqual('/dev/mapper/36005076303ffc48e0000000000000101', 748 info['device']) 749 750 self.assertEqual("/dev/sdd", info['devices'][0]['device']) 751 self.assertEqual("6", info['devices'][0]['host']) 752 self.assertEqual("0", info['devices'][0]['channel']) 753 self.assertEqual("2", info['devices'][0]['id']) 754 self.assertEqual("0", info['devices'][0]['lun']) 755 756 self.assertEqual("/dev/sdc", info['devices'][1]['device']) 757 self.assertEqual("6", info['devices'][1]['host']) 758 self.assertEqual("1", info['devices'][1]['channel']) 759 self.assertEqual("0", info['devices'][1]['id']) 760 self.assertEqual("3", info['devices'][1]['lun']) 761 762 def test_get_device_size(self): 763 mock_execute = mock.Mock() 764 self.linuxscsi._execute = mock_execute 765 size = '1024' 766 mock_execute.return_value = (size, None) 767 768 ret_size = self.linuxscsi.get_device_size('/dev/fake') 769 self.assertEqual(int(size), ret_size) 770 771 size = 'junk' 772 mock_execute.return_value = (size, None) 773 ret_size = self.linuxscsi.get_device_size('/dev/fake') 774 self.assertIsNone(ret_size) 775 776 size_bad = '1024\n' 777 size_good = 1024 778 mock_execute.return_value = (size_bad, None) 779 ret_size = self.linuxscsi.get_device_size('/dev/fake') 780 self.assertEqual(size_good, ret_size) 781 782 def test_multipath_reconfigure(self): 783 self.linuxscsi.multipath_reconfigure() 784 expected_commands = ['multipathd reconfigure'] 785 self.assertEqual(expected_commands, self.cmds) 786 787 def test_multipath_resize_map(self): 788 wwn = '1234567890123456' 789 self.linuxscsi.multipath_resize_map(wwn) 790 expected_commands = ['multipathd resize map %s' % wwn] 791 self.assertEqual(expected_commands, self.cmds) 792 793 @mock.patch.object(linuxscsi.LinuxSCSI, 'find_multipath_device_path') 794 @mock.patch.object(linuxscsi.LinuxSCSI, 'get_scsi_wwn') 795 @mock.patch.object(linuxscsi.LinuxSCSI, 'get_device_size') 796 @mock.patch.object(linuxscsi.LinuxSCSI, 'get_device_info') 797 def test_extend_volume_no_mpath(self, mock_device_info, 798 mock_device_size, 799 mock_scsi_wwn, 800 mock_find_mpath_path): 801 """Test extending a volume where there is no multipath device.""" 802 fake_device = {'host': '0', 803 'channel': '0', 804 'id': '0', 805 'lun': '1'} 806 mock_device_info.return_value = fake_device 807 808 first_size = 1024 809 second_size = 2048 810 811 mock_device_size.side_effect = [first_size, second_size] 812 wwn = '1234567890123456' 813 mock_scsi_wwn.return_value = wwn 814 mock_find_mpath_path.return_value = None 815 816 ret_size = self.linuxscsi.extend_volume(['/dev/fake']) 817 self.assertEqual(second_size, ret_size) 818 819 # because we don't mock out the echo_scsi_command 820 expected_cmds = ['tee -a /sys/bus/scsi/drivers/sd/0:0:0:1/rescan'] 821 self.assertEqual(expected_cmds, self.cmds) 822 823 @mock.patch.object(linuxscsi.LinuxSCSI, 'find_multipath_device_path') 824 @mock.patch.object(linuxscsi.LinuxSCSI, 'get_scsi_wwn') 825 @mock.patch.object(linuxscsi.LinuxSCSI, 'get_device_size') 826 @mock.patch.object(linuxscsi.LinuxSCSI, 'get_device_info') 827 def test_extend_volume_with_mpath(self, mock_device_info, 828 mock_device_size, 829 mock_scsi_wwn, 830 mock_find_mpath_path): 831 """Test extending a volume where there is a multipath device.""" 832 mock_device_info.side_effect = [{'host': host, 833 'channel': '0', 834 'id': '0', 835 'lun': '1'} for host in ['0', '1']] 836 837 mock_device_size.side_effect = [1024, 2048, 1024, 2048, 1024, 2048] 838 wwn = '1234567890123456' 839 mock_scsi_wwn.return_value = wwn 840 mock_find_mpath_path.return_value = ('/dev/mapper/dm-uuid-mpath-%s' % 841 wwn) 842 843 ret_size = self.linuxscsi.extend_volume(['/dev/fake1', '/dev/fake2'], 844 use_multipath=True) 845 self.assertEqual(2048, ret_size) 846 847 # because we don't mock out the echo_scsi_command 848 expected_cmds = ['tee -a /sys/bus/scsi/drivers/sd/0:0:0:1/rescan', 849 'tee -a /sys/bus/scsi/drivers/sd/1:0:0:1/rescan', 850 'multipathd reconfigure', 851 'multipathd resize map %s' % wwn] 852 self.assertEqual(expected_cmds, self.cmds) 853 854 @mock.patch.object(linuxscsi.LinuxSCSI, 'multipath_resize_map') 855 @mock.patch.object(linuxscsi.LinuxSCSI, 'find_multipath_device_path') 856 @mock.patch.object(linuxscsi.LinuxSCSI, 'get_scsi_wwn') 857 @mock.patch.object(linuxscsi.LinuxSCSI, 'get_device_size') 858 @mock.patch.object(linuxscsi.LinuxSCSI, 'get_device_info') 859 def test_extend_volume_with_mpath_fail(self, mock_device_info, 860 mock_device_size, 861 mock_scsi_wwn, 862 mock_find_mpath_path, 863 mock_mpath_resize_map): 864 """Test extending a volume where there is a multipath device fail.""" 865 mock_device_info.side_effect = [{'host': host, 866 'channel': '0', 867 'id': '0', 868 'lun': '1'} for host in ['0', '1']] 869 870 mock_device_size.side_effect = [1024, 2048, 1024, 2048, 1024, 2048] 871 wwn = '1234567890123456' 872 mock_scsi_wwn.return_value = wwn 873 mock_find_mpath_path.return_value = ('/dev/mapper/dm-uuid-mpath-%s' % 874 wwn) 875 876 mock_mpath_resize_map.return_value = 'fail' 877 878 ret_size = self.linuxscsi.extend_volume(['/dev/fake1', '/dev/fake2'], 879 use_multipath=True) 880 self.assertIsNone(ret_size) 881 882 # because we don't mock out the echo_scsi_command 883 expected_cmds = ['tee -a /sys/bus/scsi/drivers/sd/0:0:0:1/rescan', 884 'tee -a /sys/bus/scsi/drivers/sd/1:0:0:1/rescan', 885 'multipathd reconfigure'] 886 self.assertEqual(expected_cmds, self.cmds) 887 888 def test_process_lun_id_list(self): 889 lun_list = [2, 255, 88, 370, 5, 256] 890 result = self.linuxscsi.process_lun_id(lun_list) 891 expected = [2, 255, 88, '0x0172000000000000', 892 5, '0x0100000000000000'] 893 894 self.assertEqual(expected, result) 895 896 def test_process_lun_id_single_val_make_hex(self): 897 lun_id = 499 898 result = self.linuxscsi.process_lun_id(lun_id) 899 expected = '0x01f3000000000000' 900 self.assertEqual(expected, result) 901 902 def test_process_lun_id_single_val_make_hex_border_case(self): 903 lun_id = 256 904 result = self.linuxscsi.process_lun_id(lun_id) 905 expected = '0x0100000000000000' 906 self.assertEqual(expected, result) 907 908 def test_process_lun_id_single_var_return(self): 909 lun_id = 13 910 result = self.linuxscsi.process_lun_id(lun_id) 911 expected = 13 912 self.assertEqual(expected, result) 913 914 @mock.patch('os_brick.privileged.rootwrap.execute', return_value=('', '')) 915 def test_is_multipath_running(self, mock_exec): 916 res = linuxscsi.LinuxSCSI.is_multipath_running(False, None, mock_exec) 917 self.assertTrue(res) 918 mock_exec.assert_called_once_with( 919 'multipathd', 'show', 'status', run_as_root=True, root_helper=None) 920 921 @mock.patch.object(linuxscsi, 'LOG') 922 @mock.patch('os_brick.privileged.rootwrap.execute') 923 def test_is_multipath_running_failure( 924 self, mock_exec, mock_log 925 ): 926 mock_exec.side_effect = putils.ProcessExecutionError() 927 self.assertRaises(putils.ProcessExecutionError, 928 linuxscsi.LinuxSCSI.is_multipath_running, 929 True, None, mock_exec) 930 mock_log.error.assert_called_once() 931 932 @mock.patch.object(linuxscsi, 'LOG') 933 @mock.patch('os_brick.privileged.rootwrap.execute') 934 def test_is_multipath_running_failure_exit_code_0( 935 self, mock_exec, mock_log 936 ): 937 mock_exec.return_value = ('error receiving packet', '') 938 self.assertRaises(putils.ProcessExecutionError, 939 linuxscsi.LinuxSCSI.is_multipath_running, 940 True, None, mock_exec) 941 mock_exec.assert_called_once_with( 942 'multipathd', 'show', 'status', run_as_root=True, root_helper=None) 943 mock_log.error.assert_called_once() 944 945 @mock.patch.object(linuxscsi, 'LOG') 946 @mock.patch('os_brick.privileged.rootwrap.execute') 947 def test_is_multipath_running_failure_not_enforcing_multipath( 948 self, mock_exec, mock_log 949 ): 950 mock_exec.side_effect = putils.ProcessExecutionError() 951 res = linuxscsi.LinuxSCSI.is_multipath_running(False, None, mock_exec) 952 mock_exec.assert_called_once_with( 953 'multipathd', 'show', 'status', run_as_root=True, root_helper=None) 954 self.assertFalse(res) 955 mock_log.error.assert_not_called() 956 957 @mock.patch.object(linuxscsi, 'LOG') 958 @mock.patch('os_brick.privileged.rootwrap.execute') 959 def test_is_multipath_running_failure_not_enforcing_exit_code_0( 960 self, mock_exec, mock_log 961 ): 962 mock_exec.return_value = ('error receiving packet', '') 963 res = linuxscsi.LinuxSCSI.is_multipath_running(False, None, mock_exec) 964 mock_exec.assert_called_once_with( 965 'multipathd', 'show', 'status', run_as_root=True, root_helper=None) 966 self.assertFalse(res) 967 mock_log.error.assert_not_called() 968 969 def test_get_device_info(self): 970 ret = "[1:1:0:0] disk Vendor Array 0100 /dev/adevice\n" 971 with mock.patch.object(self.linuxscsi, '_execute') as exec_mock: 972 exec_mock.return_value = (ret, "") 973 info = self.linuxscsi.get_device_info('/dev/adevice') 974 975 exec_mock.assert_called_once_with('lsscsi') 976 self.assertEqual(info, {'channel': '1', 977 'device': '/dev/adevice', 978 'host': '1', 979 'id': '0', 980 'lun': '0'}) 981 982 @mock.patch('builtins.open') 983 def test_get_sysfs_wwn_mpath(self, open_mock): 984 wwn = '3600d0230000000000e13955cc3757800' 985 cm_open = open_mock.return_value.__enter__.return_value 986 cm_open.read.return_value = 'mpath-' + wwn 987 988 res = self.linuxscsi.get_sysfs_wwn(mock.sentinel.device_names, 'dm-1') 989 open_mock.assert_called_once_with('/sys/block/dm-1/dm/uuid') 990 self.assertEqual(wwn, res) 991 992 @mock.patch('glob.glob') 993 @mock.patch.object(linuxscsi.LinuxSCSI, 'get_sysfs_wwid') 994 def test_get_sysfs_wwn_single_designator(self, get_wwid_mock, glob_mock): 995 glob_mock.return_value = ['/dev/disk/by-id/scsi-wwid1', 996 '/dev/disk/by-id/scsi-wwid2'] 997 get_wwid_mock.return_value = 'wwid1' 998 res = self.linuxscsi.get_sysfs_wwn(mock.sentinel.device_names) 999 self.assertEqual('wwid1', res) 1000 glob_mock.assert_called_once_with('/dev/disk/by-id/scsi-*') 1001 get_wwid_mock.assert_called_once_with(mock.sentinel.device_names) 1002 1003 @mock.patch('builtins.open', side_effect=Exception) 1004 @mock.patch('glob.glob') 1005 @mock.patch.object(linuxscsi.LinuxSCSI, 'get_sysfs_wwid') 1006 def test_get_sysfs_wwn_mpath_exc(self, get_wwid_mock, glob_mock, 1007 open_mock): 1008 glob_mock.return_value = ['/dev/disk/by-id/scsi-wwid1', 1009 '/dev/disk/by-id/scsi-wwid2'] 1010 get_wwid_mock.return_value = 'wwid1' 1011 res = self.linuxscsi.get_sysfs_wwn(mock.sentinel.device_names, 'dm-1') 1012 open_mock.assert_called_once_with('/sys/block/dm-1/dm/uuid') 1013 self.assertEqual('wwid1', res) 1014 glob_mock.assert_called_once_with('/dev/disk/by-id/scsi-*') 1015 get_wwid_mock.assert_called_once_with(mock.sentinel.device_names) 1016 1017 @mock.patch('os.listdir', return_value=['sda', 'sdd']) 1018 @mock.patch('os.path.realpath', side_effect=('/other/path', 1019 '/dev/dm-5', 1020 '/dev/sda', '/dev/sdb')) 1021 @mock.patch('os.path.islink', side_effect=(False,) + (True,) * 5) 1022 @mock.patch('os.stat', side_effect=(False,) + (True,) * 4) 1023 @mock.patch('glob.glob') 1024 @mock.patch.object(linuxscsi.LinuxSCSI, 'get_sysfs_wwid') 1025 def test_get_sysfs_wwn_multiple_designators(self, get_wwid_mock, glob_mock, 1026 stat_mock, islink_mock, 1027 realpath_mock, listdir_mock): 1028 glob_mock.return_value = ['/dev/disk/by-id/scsi-fail-link', 1029 '/dev/disk/by-id/scsi-fail-stat', 1030 '/dev/disk/by-id/scsi-non-dev', 1031 '/dev/disk/by-id/scsi-another-dm', 1032 '/dev/disk/by-id/scsi-wwid1', 1033 '/dev/disk/by-id/scsi-wwid2'] 1034 1035 get_wwid_mock.return_value = 'pre-wwid' 1036 devices = ['sdb', 'sdc'] 1037 res = self.linuxscsi.get_sysfs_wwn(devices) 1038 self.assertEqual('wwid2', res) 1039 glob_mock.assert_called_once_with('/dev/disk/by-id/scsi-*') 1040 listdir_mock.assert_called_once_with('/sys/class/block/dm-5/slaves') 1041 get_wwid_mock.assert_called_once_with(devices) 1042 1043 @mock.patch('os.listdir', side_effect=[['sda', 'sdb'], ['sdc', 'sdd']]) 1044 @mock.patch('os.path.realpath', side_effect=('/dev/sde', 1045 '/dev/dm-5', 1046 '/dev/dm-6')) 1047 @mock.patch('os.path.islink', mock.Mock()) 1048 @mock.patch('os.stat', mock.Mock()) 1049 @mock.patch('glob.glob') 1050 @mock.patch.object(linuxscsi.LinuxSCSI, 'get_sysfs_wwid', return_value='') 1051 def test_get_sysfs_wwn_dm_link(self, get_wwid_mock, glob_mock, 1052 realpath_mock, listdir_mock): 1053 glob_mock.return_value = ['/dev/disk/by-id/scsi-wwid1', 1054 '/dev/disk/by-id/scsi-another-dm', 1055 '/dev/disk/by-id/scsi-our-dm'] 1056 1057 devices = ['sdc', 'sdd'] 1058 res = self.linuxscsi.get_sysfs_wwn(devices) 1059 self.assertEqual('our-dm', res) 1060 glob_mock.assert_called_once_with('/dev/disk/by-id/scsi-*') 1061 listdir_mock.assert_has_calls( 1062 [mock.call('/sys/class/block/dm-5/slaves'), 1063 mock.call('/sys/class/block/dm-6/slaves')]) 1064 get_wwid_mock.assert_called_once_with(devices) 1065 1066 @mock.patch('os.path.realpath', side_effect=('/dev/sda', '/dev/sdb')) 1067 @mock.patch('os.path.islink', return_value=True) 1068 @mock.patch('os.stat', return_value=True) 1069 @mock.patch('glob.glob') 1070 @mock.patch.object(linuxscsi.LinuxSCSI, 'get_sysfs_wwid') 1071 def test_get_sysfs_wwn_not_found(self, get_wwid_mock, glob_mock, stat_mock, 1072 islink_mock, realpath_mock): 1073 glob_mock.return_value = ['/dev/disk/by-id/scsi-wwid1', 1074 '/dev/disk/by-id/scsi-wwid2'] 1075 get_wwid_mock.return_value = 'pre-wwid' 1076 devices = ['sdc'] 1077 res = self.linuxscsi.get_sysfs_wwn(devices) 1078 self.assertEqual('', res) 1079 glob_mock.assert_called_once_with('/dev/disk/by-id/scsi-*') 1080 get_wwid_mock.assert_called_once_with(devices) 1081 1082 @mock.patch('glob.glob', return_value=[]) 1083 @mock.patch.object(linuxscsi.LinuxSCSI, 'get_sysfs_wwid') 1084 def test_get_sysfs_wwn_no_links(self, get_wwid_mock, glob_mock): 1085 get_wwid_mock.return_value = '' 1086 devices = ['sdc'] 1087 res = self.linuxscsi.get_sysfs_wwn(devices) 1088 self.assertEqual('', res) 1089 glob_mock.assert_called_once_with('/dev/disk/by-id/scsi-*') 1090 get_wwid_mock.assert_called_once_with(devices) 1091 1092 @ddt.data({'wwn_type': 't10.', 'num_val': '1'}, 1093 {'wwn_type': 'eui.', 'num_val': '2'}, 1094 {'wwn_type': 'naa.', 'num_val': '3'}) 1095 @ddt.unpack 1096 @mock.patch('builtins.open') 1097 def test_get_sysfs_wwid(self, open_mock, wwn_type, num_val): 1098 read_fail = mock.MagicMock() 1099 read_fail.__enter__.return_value.read.side_effect = IOError 1100 read_data = mock.MagicMock() 1101 read_data.__enter__.return_value.read.return_value = (wwn_type + 1102 'wwid1\n') 1103 open_mock.side_effect = (IOError, read_fail, read_data) 1104 1105 res = self.linuxscsi.get_sysfs_wwid(['sda', 'sdb', 'sdc']) 1106 self.assertEqual(num_val + 'wwid1', res) 1107 open_mock.assert_has_calls([mock.call('/sys/block/sda/device/wwid'), 1108 mock.call('/sys/block/sdb/device/wwid'), 1109 mock.call('/sys/block/sdc/device/wwid')]) 1110 1111 @mock.patch('builtins.open', side_effect=IOError) 1112 def test_get_sysfs_wwid_not_found(self, open_mock): 1113 res = self.linuxscsi.get_sysfs_wwid(['sda', 'sdb']) 1114 self.assertEqual('', res) 1115 open_mock.assert_has_calls([mock.call('/sys/block/sda/device/wwid'), 1116 mock.call('/sys/block/sdb/device/wwid')]) 1117 1118 @mock.patch.object(linuxscsi.priv_rootwrap, 'unlink_root') 1119 @mock.patch('glob.glob') 1120 @mock.patch('os.path.realpath', side_effect=['/dev/sda', '/dev/sdb', 1121 '/dev/sdc']) 1122 def test_remove_scsi_symlinks(self, realpath_mock, glob_mock, unlink_mock): 1123 paths = ['/dev/disk/by-id/scsi-wwid1', '/dev/disk/by-id/scsi-wwid2', 1124 '/dev/disk/by-id/scsi-wwid3'] 1125 glob_mock.return_value = paths 1126 self.linuxscsi._remove_scsi_symlinks(['sdb', 'sdc', 'sdd']) 1127 glob_mock.assert_called_once_with('/dev/disk/by-id/scsi-*') 1128 realpath_mock.assert_has_calls([mock.call(g) for g in paths]) 1129 unlink_mock.assert_called_once_with(no_errors=True, *paths[1:]) 1130 1131 @mock.patch.object(linuxscsi.priv_rootwrap, 'unlink_root') 1132 @mock.patch('glob.glob') 1133 @mock.patch('os.path.realpath', side_effect=['/dev/sda', '/dev/sdb']) 1134 def test_remove_scsi_symlinks_no_links(self, realpath_mock, glob_mock, 1135 unlink_mock): 1136 paths = ['/dev/disk/by-id/scsi-wwid1', '/dev/disk/by-id/scsi-wwid2'] 1137 glob_mock.return_value = paths 1138 self.linuxscsi._remove_scsi_symlinks(['sdd', 'sde']) 1139 glob_mock.assert_called_once_with('/dev/disk/by-id/scsi-*') 1140 realpath_mock.assert_has_calls([mock.call(g) for g in paths]) 1141 unlink_mock.assert_not_called() 1142 1143 @mock.patch.object(linuxscsi.priv_rootwrap, 'unlink_root') 1144 @mock.patch('glob.glob') 1145 @mock.patch('os.path.realpath', side_effect=[OSError, '/dev/sda']) 1146 def test_remove_scsi_symlinks_race_condition(self, realpath_mock, 1147 glob_mock, unlink_mock): 1148 paths = ['/dev/disk/by-id/scsi-wwid1', '/dev/disk/by-id/scsi-wwid2'] 1149 glob_mock.return_value = paths 1150 self.linuxscsi._remove_scsi_symlinks(['sda']) 1151 glob_mock.assert_called_once_with('/dev/disk/by-id/scsi-*') 1152 realpath_mock.assert_has_calls([mock.call(g) for g in paths]) 1153 unlink_mock.assert_called_once_with(paths[1], no_errors=True) 1154 1155 @mock.patch('glob.glob') 1156 def test_get_hctl_with_target(self, glob_mock): 1157 glob_mock.return_value = [ 1158 '/sys/class/iscsi_host/host3/device/session1/target3:4:5', 1159 '/sys/class/iscsi_host/host3/device/session1/target3:4:6'] 1160 res = self.linuxscsi.get_hctl('1', '2') 1161 self.assertEqual(('3', '4', '5', '2'), res) 1162 glob_mock.assert_called_once_with( 1163 '/sys/class/iscsi_host/host*/device/session1/target*') 1164 1165 @mock.patch('glob.glob') 1166 def test_get_hctl_no_target(self, glob_mock): 1167 glob_mock.side_effect = [ 1168 [], 1169 ['/sys/class/iscsi_host/host3/device/session1', 1170 '/sys/class/iscsi_host/host3/device/session1']] 1171 res = self.linuxscsi.get_hctl('1', '2') 1172 self.assertEqual(('3', '-', '-', '2'), res) 1173 glob_mock.assert_has_calls( 1174 [mock.call('/sys/class/iscsi_host/host*/device/session1/target*'), 1175 mock.call('/sys/class/iscsi_host/host*/device/session1')]) 1176 1177 @mock.patch('glob.glob', return_value=[]) 1178 def test_get_hctl_no_paths(self, glob_mock): 1179 res = self.linuxscsi.get_hctl('1', '2') 1180 self.assertIsNone(res) 1181 glob_mock.assert_has_calls( 1182 [mock.call('/sys/class/iscsi_host/host*/device/session1/target*'), 1183 mock.call('/sys/class/iscsi_host/host*/device/session1')]) 1184 1185 @mock.patch('glob.glob') 1186 def test_device_name_by_hctl(self, glob_mock): 1187 glob_mock.return_value = [ 1188 '/sys/class/scsi_host/host3/device/session1/target3:4:5/3:4:5:2/' 1189 'block/sda2', 1190 '/sys/class/scsi_host/host3/device/session1/target3:4:5/3:4:5:2/' 1191 'block/sda'] 1192 res = self.linuxscsi.device_name_by_hctl('1', ('3', '4', '5', '2')) 1193 self.assertEqual('sda', res) 1194 glob_mock.assert_called_once_with( 1195 '/sys/class/scsi_host/host3/device/session1/target3:4:5/3:4:5:2/' 1196 'block/*') 1197 1198 @mock.patch('glob.glob') 1199 def test_device_name_by_hctl_wildcards(self, glob_mock): 1200 glob_mock.return_value = [ 1201 '/sys/class/scsi_host/host3/device/session1/target3:4:5/3:4:5:2/' 1202 'block/sda2', 1203 '/sys/class/scsi_host/host3/device/session1/target3:4:5/3:4:5:2/' 1204 'block/sda'] 1205 res = self.linuxscsi.device_name_by_hctl('1', ('3', '-', '-', '2')) 1206 self.assertEqual('sda', res) 1207 glob_mock.assert_called_once_with( 1208 '/sys/class/scsi_host/host3/device/session1/target3:*:*/3:*:*:2/' 1209 'block/*') 1210 1211 @mock.patch('glob.glob', mock.Mock(return_value=[])) 1212 def test_device_name_by_hctl_no_devices(self): 1213 res = self.linuxscsi.device_name_by_hctl('1', ('4', '5', '6', '2')) 1214 self.assertIsNone(res) 1215 1216 @mock.patch.object(linuxscsi.LinuxSCSI, 'echo_scsi_command') 1217 def test_scsi_iscsi(self, echo_mock): 1218 self.linuxscsi.scan_iscsi('host', 'channel', 'target', 'lun') 1219 echo_mock.assert_called_once_with('/sys/class/scsi_host/hosthost/scan', 1220 'channel target lun') 1221 1222 def test_multipath_add_wwid(self): 1223 self.linuxscsi.multipath_add_wwid('wwid1') 1224 self.assertEqual(['multipath -a wwid1'], self.cmds) 1225 1226 def test_multipath_add_path(self): 1227 self.linuxscsi.multipath_add_path('/dev/sda') 1228 self.assertEqual(['multipathd add path /dev/sda'], self.cmds) 1229 1230 def test_multipath_del_path(self): 1231 self.linuxscsi.multipath_del_path('/dev/sda') 1232 self.assertEqual(['multipathd del path /dev/sda'], self.cmds) 1233 1234 @ddt.data({'con_props': {}, 'dev_info': {'path': mock.sentinel.path}}, 1235 {'con_props': None, 'dev_info': {'path': mock.sentinel.path}}, 1236 {'con_props': {'device_path': mock.sentinel.device_path}, 1237 'dev_info': {'path': mock.sentinel.path}}) 1238 @ddt.unpack 1239 def test_get_dev_path_device_info(self, con_props, dev_info): 1240 self.assertEqual(mock.sentinel.path, 1241 self.linuxscsi.get_dev_path(con_props, dev_info)) 1242 1243 @ddt.data({'con_props': {'device_path': mock.sentinel.device_path}, 1244 'dev_info': {'path': None}}, 1245 {'con_props': {'device_path': mock.sentinel.device_path}, 1246 'dev_info': {'path': ''}}, 1247 {'con_props': {'device_path': mock.sentinel.device_path}, 1248 'dev_info': {}}, 1249 {'con_props': {'device_path': mock.sentinel.device_path}, 1250 'dev_info': None}) 1251 @ddt.unpack 1252 def test_get_dev_path_conn_props(self, con_props, dev_info): 1253 self.assertEqual(mock.sentinel.device_path, 1254 self.linuxscsi.get_dev_path(con_props, dev_info)) 1255 1256 @ddt.data({'con_props': {'device_path': ''}, 'dev_info': {'path': None}}, 1257 {'con_props': {'device_path': None}, 'dev_info': {'path': ''}}, 1258 {'con_props': {}, 'dev_info': {}}, 1259 {'con_props': {}, 'dev_info': None}) 1260 @ddt.unpack 1261 def test_get_dev_path_no_path(self, con_props, dev_info): 1262 self.assertEqual('', self.linuxscsi.get_dev_path(con_props, dev_info)) 1263 1264 @ddt.data(('/dev/sda', '/dev/sda', False, True, None), 1265 # This checks that we ignore the was_multipath parameter if it 1266 # doesn't make sense (because the used path is the one we are 1267 # asking about) 1268 ('/dev/sda', '/dev/sda', True, True, None), 1269 ('/dev/sda', '', True, False, None), 1270 # Check for encrypted volume 1271 ('/dev/link_sda', '/dev/disk/by-path/pci-XYZ', False, True, 1272 ('/dev/sda', '/dev/mapper/crypt-pci-XYZ')), 1273 ('/dev/link_sda', '/dev/link_sdb', False, False, ('/dev/sda', 1274 '/dev/sdb')), 1275 ('/dev/link_sda', '/dev/link2_sda', False, True, ('/dev/sda', 1276 '/dev/sda'))) 1277 @ddt.unpack 1278 def test_requires_flush(self, path, path_used, was_multipath, expected, 1279 real_paths): 1280 with mock.patch('os.path.realpath', side_effect=real_paths) as mocked: 1281 self.assertEqual( 1282 expected, 1283 self.linuxscsi.requires_flush(path, path_used, was_multipath)) 1284 if real_paths: 1285 mocked.assert_has_calls([mock.call(path), 1286 mock.call(path_used)]) 1287