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