# (c) Copyright 2013 Hewlett-Packard Development Company, L.P.
#
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.

import os
from unittest import mock

import ddt

from os_brick import exception
from os_brick.initiator.connectors import base
from os_brick.initiator.connectors import fibre_channel
from os_brick.initiator import linuxfc
from os_brick.initiator import linuxscsi
from os_brick.tests.initiator import test_connector


@ddt.ddt
class FibreChannelConnectorTestCase(test_connector.ConnectorTestCase):

    def setUp(self):
        super(FibreChannelConnectorTestCase, self).setUp()
        self.connector = fibre_channel.FibreChannelConnector(
            None, execute=self.fake_execute, use_multipath=False)
        self.assertIsNotNone(self.connector)
        self.assertIsNotNone(self.connector._linuxfc)
        self.assertIsNotNone(self.connector._linuxscsi)

    def fake_get_fc_hbas(self):
        return [{'ClassDevice': 'host1',
                 'ClassDevicePath': '/sys/devices/pci0000:00/0000:00:03.0'
                                    '/0000:05:00.2/host1/fc_host/host1',
                 'dev_loss_tmo': '30',
                 'fabric_name': '0x1000000533f55566',
                 'issue_lip': '<store method only>',
                 'max_npiv_vports': '255',
                 'maxframe_size': '2048 bytes',
                 'node_name': '0x200010604b019419',
                 'npiv_vports_inuse': '0',
                 'port_id': '0x680409',
                 'port_name': '0x100010604b019419',
                 'port_state': 'Online',
                 'port_type': 'NPort (fabric via point-to-point)',
                 'speed': '10 Gbit',
                 'supported_classes': 'Class 3',
                 'supported_speeds': '10 Gbit',
                 'symbolic_name': 'Emulex 554M FV4.0.493.0 DV8.3.27',
                 'tgtid_bind_type': 'wwpn (World Wide Port Name)',
                 'uevent': None,
                 'vport_create': '<store method only>',
                 'vport_delete': '<store method only>'}]

    def fake_get_fc_hbas_with_platform(self):
        return [{'ClassDevice': 'host1',
                 'ClassDevicePath': '/sys/devices/platform/smb'
                                    '/smb:motherboard/80040000000.peu0-c0'
                                    '/pci0000:00/0000:00:03.0'
                                    '/0000:05:00.2/host1/fc_host/host1',
                 'dev_loss_tmo': '30',
                 'fabric_name': '0x1000000533f55566',
                 'issue_lip': '<store method only>',
                 'max_npiv_vports': '255',
                 'maxframe_size': '2048 bytes',
                 'node_name': '0x200010604b019419',
                 'npiv_vports_inuse': '0',
                 'port_id': '0x680409',
                 'port_name': '0x100010604b019419',
                 'port_state': 'Online',
                 'port_type': 'NPort (fabric via point-to-point)',
                 'speed': '10 Gbit',
                 'supported_classes': 'Class 3',
                 'supported_speeds': '10 Gbit',
                 'symbolic_name': 'Emulex 554M FV4.0.493.0 DV8.3.27',
                 'tgtid_bind_type': 'wwpn (World Wide Port Name)',
                 'uevent': None,
                 'vport_create': '<store method only>',
                 'vport_delete': '<store method only>'}]

    def fake_get_fc_hbas_info(self):
        hbas = self.fake_get_fc_hbas()
        info = [{'port_name': hbas[0]['port_name'].replace('0x', ''),
                 'node_name': hbas[0]['node_name'].replace('0x', ''),
                 'host_device': hbas[0]['ClassDevice'],
                 'device_path': hbas[0]['ClassDevicePath']}]
        return info

    def fake_get_fc_hbas_info_with_platform(self):
        hbas = self.fake_get_fc_hbas_with_platform()
        info = [{'port_name': hbas[0]['port_name'].replace('0x', ''),
                 'node_name': hbas[0]['node_name'].replace('0x', ''),
                 'host_device': hbas[0]['ClassDevice'],
                 'device_path': hbas[0]['ClassDevicePath']}]
        return info

    def fibrechan_connection(self, volume, location, wwn, lun=1):
        return {'driver_volume_type': 'fibrechan',
                'data': {
                    'volume_id': volume['id'],
                    'target_portal': location,
                    'target_wwn': wwn,
                    'target_lun': lun,
                }}

    @mock.patch.object(linuxfc.LinuxFibreChannel, 'get_fc_hbas')
    def test_get_connector_properties(self, mock_hbas):
        mock_hbas.return_value = self.fake_get_fc_hbas()
        multipath = True
        enforce_multipath = True
        props = fibre_channel.FibreChannelConnector.get_connector_properties(
            'sudo', multipath=multipath,
            enforce_multipath=enforce_multipath)

        hbas = self.fake_get_fc_hbas()
        expected_props = {'wwpns': [hbas[0]['port_name'].replace('0x', '')],
                          'wwnns': [hbas[0]['node_name'].replace('0x', '')]}
        self.assertEqual(expected_props, props)

    def test_get_search_path(self):
        search_path = self.connector.get_search_path()
        expected = "/dev/disk/by-path"
        self.assertEqual(expected, search_path)

    def test_get_pci_num(self):
        hba = {'device_path': "/sys/devices/pci0000:00/0000:00:03.0"
                              "/0000:05:00.3/host2/fc_host/host2"}
        platform, pci_num = self.connector._get_pci_num(hba)
        self.assertEqual("0000:05:00.3", pci_num)
        self.assertIsNone(platform)

        hba = {'device_path': "/sys/devices/pci0000:00/0000:00:03.0"
                              "/0000:05:00.3/0000:06:00.6/host2/fc_host/host2"}
        platform, pci_num = self.connector._get_pci_num(hba)
        self.assertEqual("0000:06:00.6", pci_num)
        self.assertIsNone(platform)

        hba = {'device_path': "/sys/devices/pci0000:20/0000:20:03.0"
                              "/0000:21:00.2/net/ens2f2/ctlr_2/host3"
                              "/fc_host/host3"}
        platform, pci_num = self.connector._get_pci_num(hba)
        self.assertEqual("0000:21:00.2", pci_num)
        self.assertIsNone(platform)

    def test_get_pci_num_with_platform(self):
        hba = {'device_path': "/sys/devices/platform/smb/smb:motherboard/"
                              "80040000000.peu0-c0/pci0000:00/0000:00:03.0"
                              "/0000:05:00.3/host2/fc_host/host2"}
        platform, pci_num = self.connector._get_pci_num(hba)
        self.assertEqual("0000:05:00.3", pci_num)
        self.assertEqual("platform-80040000000.peu0-c0", platform)

        hba = {'device_path': "/sys/devices/platform/smb/smb:motherboard"
                              "/80040000000.peu0-c0/pci0000:00/0000:00:03.0"
                              "/0000:05:00.3/0000:06:00.6/host2/fc_host/host2"}
        platform, pci_num = self.connector._get_pci_num(hba)
        self.assertEqual("0000:06:00.6", pci_num)
        self.assertEqual("platform-80040000000.peu0-c0", platform)

        hba = {'device_path': "/sys/devices/platform/smb"
                              "/smb:motherboard/80040000000.peu0-c0/pci0000:20"
                              "/0000:20:03.0/0000:21:00.2"
                              "/net/ens2f2/ctlr_2/host3/fc_host/host3"}
        platform, pci_num = self.connector._get_pci_num(hba)
        self.assertEqual("0000:21:00.2", pci_num)
        self.assertEqual("platform-80040000000.peu0-c0", platform)

    @mock.patch.object(os.path, 'exists', return_value=True)
    @mock.patch.object(linuxfc.LinuxFibreChannel, 'get_fc_hbas')
    @mock.patch.object(linuxfc.LinuxFibreChannel, 'get_fc_hbas_info')
    def test_get_volume_paths(self, fake_fc_hbas_info,
                              fake_fc_hbas, fake_exists):
        fake_fc_hbas.side_effect = self.fake_get_fc_hbas
        fake_fc_hbas_info.side_effect = self.fake_get_fc_hbas_info

        name = 'volume-00000001'
        vol = {'id': 1, 'name': name}
        location = '10.0.2.15:3260'
        wwn = '1234567890123456'
        connection_info = self.fibrechan_connection(vol, location, wwn)
        conn_data = self.connector._add_targets_to_connection_properties(
            connection_info['data']
        )
        volume_paths = self.connector.get_volume_paths(conn_data)

        expected = ['/dev/disk/by-path/pci-0000:05:00.2'
                    '-fc-0x1234567890123456-lun-1']
        self.assertEqual(expected, volume_paths)

    @mock.patch.object(os.path, 'exists', return_value=True)
    @mock.patch.object(linuxfc.LinuxFibreChannel, 'get_fc_hbas')
    @mock.patch.object(linuxfc.LinuxFibreChannel, 'get_fc_hbas_info')
    def test_get_volume_paths_with_platform(self, fake_fc_hbas_info,
                                            fake_fc_hbas, fake_exists):
        fake_fc_hbas.side_effect = self.fake_get_fc_hbas_with_platform
        fake_fc_hbas_info.side_effect \
            = self.fake_get_fc_hbas_info_with_platform

        name = 'volume-00000001'
        vol = {'id': 1, 'name': name}
        location = '10.0.2.15:3260'
        wwn = '1234567890123456'
        connection_info = self.fibrechan_connection(vol, location, wwn)
        conn_data = self.connector._add_targets_to_connection_properties(
            connection_info['data']
        )
        volume_paths = self.connector.get_volume_paths(conn_data)

        expected = ['/dev/disk/by-path'
                    '/platform-80040000000.peu0-c0-pci-0000:05:00.2'
                    '-fc-0x1234567890123456-lun-1']
        self.assertEqual(expected, volume_paths)

    @mock.patch.object(linuxscsi.LinuxSCSI, 'wait_for_rw')
    @mock.patch.object(os.path, 'exists', return_value=True)
    @mock.patch.object(os.path, 'realpath', return_value='/dev/sdb')
    @mock.patch.object(linuxfc.LinuxFibreChannel, 'get_fc_hbas')
    @mock.patch.object(linuxfc.LinuxFibreChannel, 'get_fc_hbas_info')
    @mock.patch.object(linuxscsi.LinuxSCSI, 'remove_scsi_device')
    @mock.patch.object(linuxscsi.LinuxSCSI, 'get_scsi_wwn')
    @mock.patch.object(linuxscsi.LinuxSCSI, 'get_device_info')
    @mock.patch.object(base.BaseLinuxConnector, 'check_valid_device')
    def test_connect_volume(self, check_valid_device_mock,
                            get_device_info_mock,
                            get_scsi_wwn_mock,
                            remove_device_mock,
                            get_fc_hbas_info_mock,
                            get_fc_hbas_mock,
                            realpath_mock,
                            exists_mock,
                            wait_for_rw_mock):
        check_valid_device_mock.return_value = True
        get_fc_hbas_mock.side_effect = self.fake_get_fc_hbas
        get_fc_hbas_info_mock.side_effect = self.fake_get_fc_hbas_info

        wwn = '1234567890'
        multipath_devname = '/dev/md-1'
        devices = {"device": multipath_devname,
                   "id": wwn,
                   "devices": [{'device': '/dev/sdb',
                                'address': '1:0:0:1',
                                'host': 1, 'channel': 0,
                                'id': 0, 'lun': 1}]}
        get_device_info_mock.return_value = devices['devices'][0]
        get_scsi_wwn_mock.return_value = wwn

        location = '10.0.2.15:3260'
        name = 'volume-00000001'
        vol = {'id': 1, 'name': name}
        # Should work for string, unicode, and list
        wwns_luns = [
            ('1234567890123456', 1),
            (str('1234567890123456'), 1),
            (['1234567890123456', '1234567890123457'], 1),
            (['1234567890123456', '1234567890123457'], 1),
        ]
        for wwn, lun in wwns_luns:
            connection_info = self.fibrechan_connection(vol, location,
                                                        wwn, lun)
            dev_info = self.connector.connect_volume(connection_info['data'])
            exp_wwn = wwn[0] if isinstance(wwn, list) else wwn
            dev_str = ('/dev/disk/by-path/pci-0000:05:00.2-fc-0x%s-lun-1' %
                       exp_wwn)
            self.assertEqual(dev_info['type'], 'block')
            self.assertEqual(dev_info['path'], dev_str)
            self.assertNotIn('multipath_id', dev_info)
            self.assertNotIn('devices', dev_info)

            self.connector.disconnect_volume(connection_info['data'], dev_info)
            expected_commands = []
            self.assertEqual(expected_commands, self.cmds)

        # Should not work for anything other than string, unicode, and list
        connection_info = self.fibrechan_connection(vol, location, 123)
        self.assertRaises(exception.VolumePathsNotFound,
                          self.connector.connect_volume,
                          connection_info['data'])

        get_fc_hbas_mock.side_effect = [[]]
        get_fc_hbas_info_mock.side_effect = [[]]
        self.assertRaises(exception.VolumePathsNotFound,
                          self.connector.connect_volume,
                          connection_info['data'])

    @mock.patch.object(linuxscsi.LinuxSCSI, 'find_multipath_device_path')
    def _test_connect_volume_multipath(self, get_device_info_mock,
                                       get_scsi_wwn_mock,
                                       get_fc_hbas_info_mock,
                                       get_fc_hbas_mock,
                                       realpath_mock,
                                       exists_mock,
                                       wait_for_rw_mock,
                                       find_mp_dev_mock,
                                       access_mode,
                                       should_wait_for_rw,
                                       find_mp_device_path_mock):
        self.connector.use_multipath = True
        get_fc_hbas_mock.side_effect = self.fake_get_fc_hbas
        get_fc_hbas_info_mock.side_effect = self.fake_get_fc_hbas_info

        wwn = '1234567890'
        multipath_devname = '/dev/md-1'
        devices = {"device": multipath_devname,
                   "id": wwn,
                   "devices": [{'device': '/dev/sdb',
                                'address': '1:0:0:1',
                                'host': 1, 'channel': 0,
                                'id': 0, 'lun': 1},
                               {'device': '/dev/sdc',
                                'address': '1:0:0:2',
                                'host': 1, 'channel': 0,
                                'id': 0, 'lun': 1}]}
        get_device_info_mock.side_effect = devices['devices']
        get_scsi_wwn_mock.return_value = wwn

        location = '10.0.2.15:3260'
        name = 'volume-00000001'
        vol = {'id': 1, 'name': name}
        initiator_wwn = ['1234567890123456', '1234567890123457']

        find_mp_device_path_mock.return_value = '/dev/mapper/mpatha'
        find_mp_dev_mock.return_value = {"device": "dm-3",
                                         "id": wwn,
                                         "name": "mpatha"}

        connection_info = self.fibrechan_connection(vol, location,
                                                    initiator_wwn)
        connection_info['data']['access_mode'] = access_mode

        self.connector.connect_volume(connection_info['data'])

        self.assertEqual(should_wait_for_rw, wait_for_rw_mock.called)

        self.connector.disconnect_volume(connection_info['data'],
                                         devices['devices'][0])
        expected_commands = [
            'multipath -f ' + find_mp_device_path_mock.return_value,
            'tee -a /sys/block/sdb/device/delete',
            'tee -a /sys/block/sdc/device/delete',
        ]
        self.assertEqual(expected_commands, self.cmds)
        return connection_info

    @mock.patch.object(linuxscsi.LinuxSCSI, 'find_multipath_device')
    @mock.patch.object(linuxscsi.LinuxSCSI, 'wait_for_rw')
    @mock.patch.object(os.path, 'exists', return_value=True)
    @mock.patch.object(os.path, 'realpath', return_value='/dev/sdb')
    @mock.patch.object(linuxfc.LinuxFibreChannel, 'get_fc_hbas')
    @mock.patch.object(linuxfc.LinuxFibreChannel, 'get_fc_hbas_info')
    @mock.patch.object(linuxscsi.LinuxSCSI, 'get_scsi_wwn')
    @mock.patch.object(linuxscsi.LinuxSCSI, 'get_device_info')
    @mock.patch.object(base.BaseLinuxConnector, 'check_valid_device')
    def test_connect_volume_multipath_rw(self, check_valid_device_mock,
                                         get_device_info_mock,
                                         get_scsi_wwn_mock,
                                         get_fc_hbas_info_mock,
                                         get_fc_hbas_mock,
                                         realpath_mock,
                                         exists_mock,
                                         wait_for_rw_mock,
                                         find_mp_dev_mock):

        check_valid_device_mock.return_value = True
        self._test_connect_volume_multipath(get_device_info_mock,
                                            get_scsi_wwn_mock,
                                            get_fc_hbas_info_mock,
                                            get_fc_hbas_mock,
                                            realpath_mock,
                                            exists_mock,
                                            wait_for_rw_mock,
                                            find_mp_dev_mock,
                                            'rw',
                                            True)

    @mock.patch.object(linuxscsi.LinuxSCSI, 'find_multipath_device')
    @mock.patch.object(linuxscsi.LinuxSCSI, 'wait_for_rw')
    @mock.patch.object(os.path, 'exists', return_value=True)
    @mock.patch.object(os.path, 'realpath', return_value='/dev/sdb')
    @mock.patch.object(linuxfc.LinuxFibreChannel, 'get_fc_hbas')
    @mock.patch.object(linuxfc.LinuxFibreChannel, 'get_fc_hbas_info')
    @mock.patch.object(linuxscsi.LinuxSCSI, 'get_scsi_wwn')
    @mock.patch.object(linuxscsi.LinuxSCSI, 'get_device_info')
    @mock.patch.object(base.BaseLinuxConnector, 'check_valid_device')
    def test_connect_volume_multipath_no_access_mode(self,
                                                     check_valid_device_mock,
                                                     get_device_info_mock,
                                                     get_scsi_wwn_mock,
                                                     get_fc_hbas_info_mock,
                                                     get_fc_hbas_mock,
                                                     realpath_mock,
                                                     exists_mock,
                                                     wait_for_rw_mock,
                                                     find_mp_dev_mock):

        check_valid_device_mock.return_value = True
        self._test_connect_volume_multipath(get_device_info_mock,
                                            get_scsi_wwn_mock,
                                            get_fc_hbas_info_mock,
                                            get_fc_hbas_mock,
                                            realpath_mock,
                                            exists_mock,
                                            wait_for_rw_mock,
                                            find_mp_dev_mock,
                                            None,
                                            True)

    @mock.patch.object(linuxscsi.LinuxSCSI, 'find_multipath_device')
    @mock.patch.object(linuxscsi.LinuxSCSI, 'wait_for_rw')
    @mock.patch.object(os.path, 'exists', return_value=True)
    @mock.patch.object(os.path, 'realpath', return_value='/dev/sdb')
    @mock.patch.object(linuxfc.LinuxFibreChannel, 'get_fc_hbas')
    @mock.patch.object(linuxfc.LinuxFibreChannel, 'get_fc_hbas_info')
    @mock.patch.object(linuxscsi.LinuxSCSI, 'get_scsi_wwn')
    @mock.patch.object(linuxscsi.LinuxSCSI, 'get_device_info')
    @mock.patch.object(base.BaseLinuxConnector, 'check_valid_device')
    def test_connect_volume_multipath_ro(self, check_valid_device_mock,
                                         get_device_info_mock,
                                         get_scsi_wwn_mock,
                                         get_fc_hbas_info_mock,
                                         get_fc_hbas_mock,
                                         realpath_mock,
                                         exists_mock,
                                         wait_for_rw_mock,
                                         find_mp_dev_mock):

        check_valid_device_mock.return_value = True
        self._test_connect_volume_multipath(get_device_info_mock,
                                            get_scsi_wwn_mock,
                                            get_fc_hbas_info_mock,
                                            get_fc_hbas_mock,
                                            realpath_mock,
                                            exists_mock,
                                            wait_for_rw_mock,
                                            find_mp_dev_mock,
                                            'ro',
                                            False)

    @mock.patch.object(base.BaseLinuxConnector, '_discover_mpath_device')
    @mock.patch.object(linuxscsi.LinuxSCSI, 'find_multipath_device')
    @mock.patch.object(linuxscsi.LinuxSCSI, 'wait_for_rw')
    @mock.patch.object(os.path, 'exists', return_value=True)
    @mock.patch.object(os.path, 'realpath', return_value='/dev/sdb')
    @mock.patch.object(linuxfc.LinuxFibreChannel, 'get_fc_hbas')
    @mock.patch.object(linuxfc.LinuxFibreChannel, 'get_fc_hbas_info')
    @mock.patch.object(linuxscsi.LinuxSCSI, 'get_scsi_wwn')
    @mock.patch.object(linuxscsi.LinuxSCSI, 'get_device_info')
    @mock.patch.object(base.BaseLinuxConnector, 'check_valid_device')
    def test_connect_volume_multipath_not_found(self,
                                                check_valid_device_mock,
                                                get_device_info_mock,
                                                get_scsi_wwn_mock,
                                                get_fc_hbas_info_mock,
                                                get_fc_hbas_mock,
                                                realpath_mock,
                                                exists_mock,
                                                wait_for_rw_mock,
                                                find_mp_dev_mock,
                                                discover_mp_dev_mock):
        check_valid_device_mock.return_value = True
        discover_mp_dev_mock.return_value = ("/dev/disk/by-path/something",
                                             None)

        connection_info = self._test_connect_volume_multipath(
            get_device_info_mock, get_scsi_wwn_mock, get_fc_hbas_info_mock,
            get_fc_hbas_mock, realpath_mock, exists_mock, wait_for_rw_mock,
            find_mp_dev_mock, 'rw', False)

        self.assertNotIn('multipathd_id', connection_info['data'])
        # Ensure we don't call it with the real path
        device_name = discover_mp_dev_mock.call_args[0][-1]
        self.assertNotEqual(realpath_mock.return_value, device_name)

    @mock.patch.object(fibre_channel.FibreChannelConnector, 'get_volume_paths')
    def test_extend_volume_no_path(self, mock_volume_paths):
        mock_volume_paths.return_value = []
        volume = {'id': 'fake_uuid'}
        wwn = '1234567890123456'
        connection_info = self.fibrechan_connection(volume,
                                                    "10.0.2.15:3260",
                                                    wwn)

        self.assertRaises(exception.VolumePathsNotFound,
                          self.connector.extend_volume,
                          connection_info['data'])

    @mock.patch.object(linuxscsi.LinuxSCSI, 'extend_volume')
    @mock.patch.object(fibre_channel.FibreChannelConnector, 'get_volume_paths')
    def test_extend_volume(self, mock_volume_paths, mock_scsi_extend):
        fake_new_size = 1024
        mock_volume_paths.return_value = ['/dev/vdx']
        mock_scsi_extend.return_value = fake_new_size
        volume = {'id': 'fake_uuid'}
        wwn = '1234567890123456'
        connection_info = self.fibrechan_connection(volume,
                                                    "10.0.2.15:3260",
                                                    wwn)
        new_size = self.connector.extend_volume(connection_info['data'])
        self.assertEqual(fake_new_size, new_size)

    @mock.patch.object(os.path, 'isdir')
    def test_get_all_available_volumes_path_not_dir(self, mock_isdir):
        mock_isdir.return_value = False
        expected = []
        actual = self.connector.get_all_available_volumes()
        self.assertCountEqual(expected, actual)

    @mock.patch('eventlet.greenthread.sleep', mock.Mock())
    @mock.patch.object(linuxscsi.LinuxSCSI, 'find_multipath_device')
    @mock.patch.object(linuxscsi.LinuxSCSI, 'wait_for_rw')
    @mock.patch.object(os.path, 'exists', return_value=True)
    @mock.patch.object(os.path, 'realpath', return_value='/dev/sdb')
    @mock.patch.object(linuxfc.LinuxFibreChannel, 'get_fc_hbas')
    @mock.patch.object(linuxfc.LinuxFibreChannel, 'get_fc_hbas_info')
    @mock.patch.object(linuxscsi.LinuxSCSI, 'get_scsi_wwn')
    @mock.patch.object(linuxscsi.LinuxSCSI, 'get_device_info')
    @mock.patch.object(base.BaseLinuxConnector, 'check_valid_device')
    def test_connect_volume_device_not_valid(self, check_valid_device_mock,
                                             get_device_info_mock,
                                             get_scsi_wwn_mock,
                                             get_fc_hbas_info_mock,
                                             get_fc_hbas_mock,
                                             realpath_mock,
                                             exists_mock,
                                             wait_for_rw_mock,
                                             find_mp_dev_mock):

        check_valid_device_mock.return_value = False
        self.assertRaises(exception.NoFibreChannelVolumeDeviceFound,
                          self._test_connect_volume_multipath,
                          get_device_info_mock,
                          get_scsi_wwn_mock,
                          get_fc_hbas_info_mock,
                          get_fc_hbas_mock,
                          realpath_mock,
                          exists_mock,
                          wait_for_rw_mock,
                          find_mp_dev_mock,
                          'rw',
                          True)

    @ddt.data(
        {
            "target_info": {
                "target_lun": 1,
                "target_wwn": '1234567890123456',
            },
            "expected_targets": [
                ('1234567890123456', 1)
            ]
        },
        {
            "target_info": {
                "target_lun": 1,
                "target_wwn": ['1234567890123456', '1234567890123457'],
            },
            "expected_targets": [
                ('1234567890123456', 1),
                ('1234567890123457', 1),
            ]
        },
        {
            "target_info": {
                "target_luns": [1, 1],
                "target_wwn": ['1234567890123456', '1234567890123457'],
            },
            "expected_targets": [
                ('1234567890123456', 1),
                ('1234567890123457', 1),
            ]
        },
        {
            "target_info": {
                "target_luns": [1, 2],
                "target_wwn": ['1234567890123456', '1234567890123457'],
            },
            "expected_targets": [
                ('1234567890123456', 1),
                ('1234567890123457', 2),
            ]
        },
        {
            "target_info": {
                "target_luns": [1, 1],
                "target_wwns": ['1234567890123456', '1234567890123457'],
            },
            "expected_targets": [
                ('1234567890123456', 1),
                ('1234567890123457', 1),
            ]
        },
        {
            "target_info": {
                "target_lun": 7,
                "target_luns": [1, 1],
                "target_wwn": 'foo',
                "target_wwns": ['1234567890123456', '1234567890123457'],
            },
            "expected_targets": [
                ('1234567890123456', 1),
                ('1234567890123457', 1),
            ]
        },
        # Add the zone map in now
        {
            "target_info": {
                "target_lun": 1,
                "target_wwn": '1234567890123456',
            },
            "expected_targets": [
                ('1234567890123456', 1)
            ],
            "itmap": {
                '0004567890123456': ['1234567890123456']
            },
            "expected_map": {
                '0004567890123456': [('1234567890123456', 1)]
            }
        },
        {
            "target_info": {
                "target_lun": 1,
                "target_wwn": ['1234567890123456', '1234567890123457'],
            },
            "expected_targets": [
                ('1234567890123456', 1),
                ('1234567890123457', 1),
            ],
            "itmap": {
                '0004567890123456': ['1234567890123456',
                                     '1234567890123457']
            },
            "expected_map": {
                '0004567890123456': [('1234567890123456', 1),
                                     ('1234567890123457', 1)]
            }
        },
        {
            "target_info": {
                "target_luns": [1, 2],
                "target_wwn": ['1234567890123456', '1234567890123457'],
            },
            "expected_targets": [
                ('1234567890123456', 1),
                ('1234567890123457', 2),
            ],
            "itmap": {
                '0004567890123456': ['1234567890123456'],
                '1004567890123456': ['1234567890123457'],
            },
            "expected_map": {
                '0004567890123456': [('1234567890123456', 1)],
                '1004567890123456': [('1234567890123457', 2)],
            }
        },
        {
            "target_info": {
                "target_luns": [1, 2],
                "target_wwn": ['1234567890123456', '1234567890123457'],
            },
            "expected_targets": [
                ('1234567890123456', 1),
                ('1234567890123457', 2),
            ],
            "itmap": {
                '0004567890123456': ['1234567890123456',
                                     '1234567890123457']
            },
            "expected_map": {
                '0004567890123456': [('1234567890123456', 1),
                                     ('1234567890123457', 2)]
            }
        },
        {
            "target_info": {
                "target_lun": 1,
                "target_wwn": ['20320002AC01E166', '21420002AC01E166',
                               '20410002AC01E166', '21410002AC01E166']
            },
            "expected_targets": [
                ('20320002ac01e166', 1),
                ('21420002ac01e166', 1),
                ('20410002ac01e166', 1),
                ('21410002ac01e166', 1)
            ],
            "itmap": {
                '10001409DCD71FF6': ['20320002AC01E166', '21420002AC01E166'],
                '10001409DCD71FF7': ['20410002AC01E166', '21410002AC01E166']
            },
            "expected_map": {
                '10001409dcd71ff6': [('20320002ac01e166', 1),
                                     ('21420002ac01e166', 1)],
                '10001409dcd71ff7': [('20410002ac01e166', 1),
                                     ('21410002ac01e166', 1)]
            }
        },
    )
    @ddt.unpack
    def test__add_targets_to_connection_properties(self, target_info,
                                                   expected_targets,
                                                   itmap=None,
                                                   expected_map=None):
        volume = {'id': 'fake_uuid'}
        wwn = '1234567890123456'
        conn = self.fibrechan_connection(volume, "10.0.2.15:3260", wwn)
        conn['data'].update(target_info)

        conn['data']['initiator_target_map'] = itmap

        connection_info = self.connector._add_targets_to_connection_properties(
            conn['data'])
        self.assertIn('targets', connection_info)
        self.assertEqual(expected_targets, connection_info['targets'])

        # Check that we turn to lowercase target wwns
        key = 'target_wwns' if 'target_wwns' in target_info else 'target_wwn'
        wwns = target_info.get(key)
        wwns = [wwns] if isinstance(wwns, str) else wwns
        wwns = [w.lower() for w in wwns]
        if wwns:
            self.assertEqual(wwns, conn['data'][key])

        if itmap:
            self.assertIn('initiator_target_lun_map', connection_info)
            self.assertEqual(expected_map,
                             connection_info['initiator_target_lun_map'])

    @ddt.data(('/dev/mapper/<WWN>', True),
              ('/dev/mapper/mpath0', True),
              # Check real devices are properly detected as non multipaths
              ('/dev/sda', False),
              ('/dev/disk/by-path/pci-1-fc-1-lun-1', False))
    @ddt.unpack
    @mock.patch('os_brick.initiator.linuxscsi.LinuxSCSI.remove_scsi_device')
    @mock.patch('os_brick.initiator.linuxscsi.LinuxSCSI.requires_flush')
    @mock.patch('os_brick.utils.get_dev_path')
    def test__remove_devices(self, path_used, was_multipath, get_dev_path_mock,
                             flush_mock, remove_mock):
        exc = exception.ExceptionChainer()
        get_dev_path_mock.return_value = path_used
        self.connector._remove_devices(mock.sentinel.con_props,
                                       [{'device': '/dev/sda'}],
                                       mock.sentinel.device_info,
                                       force=False, exc=exc)
        self.assertFalse(bool(exc))
        get_dev_path_mock.assert_called_once_with(mock.sentinel.con_props,
                                                  mock.sentinel.device_info)
        flush_mock.assert_called_once_with('/dev/sda', path_used,
                                           was_multipath)
        remove_mock.assert_called_once_with('/dev/sda',
                                            flush=flush_mock.return_value)

    @ddt.data(('/dev/mapper/<WWN>', True),
              ('/dev/mapper/mpath0', True),
              # Check real devices are properly detected as non multipaths
              ('/dev/sda', False),
              ('/dev/disk/by-path/pci-1-fc-1-lun-1', False))
    @ddt.unpack
    @mock.patch('os_brick.initiator.linuxscsi.LinuxSCSI.remove_scsi_device')
    @mock.patch('os_brick.initiator.linuxscsi.LinuxSCSI.requires_flush')
    @mock.patch('os_brick.utils.get_dev_path')
    def test__remove_devices_fails(self, path_used, was_multipath,
                                   get_dev_path_mock, flush_mock, remove_mock):
        exc = exception.ExceptionChainer()
        get_dev_path_mock.return_value = path_used
        remove_mock.side_effect = Exception
        self.connector._remove_devices(mock.sentinel.con_props,
                                       [{'device': '/dev/sda'},
                                        {'device': '/dev/sdb'}],
                                       mock.sentinel.device_info,
                                       force=True, exc=exc)
        self.assertTrue(bool(exc))
        get_dev_path_mock.assert_called_once_with(mock.sentinel.con_props,
                                                  mock.sentinel.device_info)

        expect_flush = [mock.call('/dev/sda', path_used, was_multipath),
                        mock.call('/dev/sdb', path_used, was_multipath)]
        self.assertEqual(len(expect_flush), flush_mock.call_count)
        flush_mock.assert_has_calls(expect_flush)

        expect_remove = [mock.call('/dev/sda', flush=flush_mock.return_value),
                         mock.call('/dev/sdb', flush=flush_mock.return_value)]
        self.assertEqual(len(expect_remove), remove_mock.call_count)
        remove_mock.assert_has_calls(expect_remove)

    @mock.patch.object(linuxscsi.LinuxSCSI, 'find_multipath_device')
    @mock.patch.object(linuxscsi.LinuxSCSI, 'wait_for_rw')
    @mock.patch.object(os.path, 'exists', return_value=True)
    @mock.patch.object(os.path, 'realpath', return_value='/dev/sdb')
    @mock.patch.object(linuxfc.LinuxFibreChannel, 'get_fc_hbas')
    @mock.patch.object(linuxfc.LinuxFibreChannel, 'get_fc_hbas_info')
    @mock.patch.object(linuxscsi.LinuxSCSI, 'get_scsi_wwn')
    @mock.patch.object(linuxscsi.LinuxSCSI, 'get_device_info')
    @mock.patch.object(linuxscsi.LinuxSCSI, 'find_multipath_device_path')
    @mock.patch.object(base.BaseLinuxConnector, 'check_valid_device')
    def test_disconnect_volume(self, check_valid_device_mock,
                               find_mp_device_path_mock,
                               get_device_info_mock,
                               get_scsi_wwn_mock,
                               get_fc_hbas_info_mock,
                               get_fc_hbas_mock,
                               realpath_mock,
                               exists_mock,
                               wait_for_rw_mock,
                               find_mp_dev_mock):

        check_valid_device_mock.return_value = True
        self.connector.use_multipath = True
        get_fc_hbas_mock.side_effect = self.fake_get_fc_hbas
        get_fc_hbas_info_mock.side_effect = self.fake_get_fc_hbas_info

        wwn = '1234567890'
        multipath_devname = '/dev/md-1'
        devices = {"device": multipath_devname,
                   "id": wwn,
                   "devices": [{'device': '/dev/sdb',
                                'address': '1:0:0:1',
                                'host': 1, 'channel': 0,
                                'id': 0, 'lun': 1},
                               {'device': '/dev/sdc',
                                'address': '1:0:0:2',
                                'host': 1, 'channel': 0,
                                'id': 0, 'lun': 1}]}
        get_device_info_mock.side_effect = devices['devices']
        get_scsi_wwn_mock.return_value = wwn

        location = '10.0.2.15:3260'
        name = 'volume-00000001'
        vol = {'id': 1, 'name': name}
        initiator_wwn = ['1234567890123456', '1234567890123457']

        find_mp_device_path_mock.return_value = '/dev/mapper/mpatha'
        find_mp_dev_mock.return_value = {"device": "dm-3",
                                         "id": wwn,
                                         "name": "mpatha"}

        connection_info = self.fibrechan_connection(vol, location,
                                                    initiator_wwn)
        self.connector.disconnect_volume(connection_info['data'],
                                         devices['devices'][0])
        expected_commands = [
            'multipath -f ' + find_mp_device_path_mock.return_value,
            'tee -a /sys/block/sdb/device/delete',
            'tee -a /sys/block/sdc/device/delete',
        ]
        self.assertEqual(expected_commands, self.cmds)

    @ddt.data((False, Exception), (True, Exception), (False, None))
    @ddt.unpack
    @mock.patch.object(fibre_channel.FibreChannelConnector, '_remove_devices')
    @mock.patch.object(linuxscsi.LinuxSCSI, 'multipath_del_map')
    @mock.patch.object(linuxscsi.LinuxSCSI, 'flush_multipath_device')
    @mock.patch.object(linuxscsi.LinuxSCSI, 'wait_for_rw')
    @mock.patch.object(os.path, 'exists', return_value=True)
    @mock.patch.object(os.path, 'realpath')
    @mock.patch.object(linuxfc.LinuxFibreChannel, 'get_fc_hbas')
    @mock.patch.object(linuxfc.LinuxFibreChannel, 'get_fc_hbas_info')
    @mock.patch.object(linuxscsi.LinuxSCSI, 'get_scsi_wwn')
    @mock.patch.object(linuxscsi.LinuxSCSI, 'get_device_info')
    @mock.patch.object(linuxscsi.LinuxSCSI, 'find_multipath_device_path')
    @mock.patch.object(base.BaseLinuxConnector, 'check_valid_device')
    def test_disconnect_volume_fails(self, ignore_exc, side_effect,
                                     check_valid_device_mock,
                                     find_mp_device_path_mock,
                                     get_device_info_mock,
                                     get_scsi_wwn_mock,
                                     get_fc_hbas_info_mock,
                                     get_fc_hbas_mock,
                                     realpath_mock,
                                     exists_mock,
                                     wait_for_rw_mock,
                                     flush_mock,
                                     del_map_mock,
                                     remove_mock):

        flush_mock.side_effect = side_effect
        del_map_mock.side_effect = side_effect

        check_valid_device_mock.return_value = True
        self.connector.use_multipath = True
        get_fc_hbas_mock.side_effect = self.fake_get_fc_hbas
        get_fc_hbas_info_mock.side_effect = self.fake_get_fc_hbas_info

        wwn = '360002ac00000000000000b860000741c'
        multipath_devname = f'/dev/disk/by-id/dm-uuid-mpath-{wwn}'
        realpath_mock.return_value = '/dev/dm-1'
        devices = {"device": multipath_devname,
                   "id": wwn,
                   "devices": [{'device': '/dev/sdb',
                                'address': '1:0:0:1',
                                'host': 1, 'channel': 0,
                                'id': 0, 'lun': 1},
                               {'device': '/dev/sdc',
                                'address': '1:0:0:2',
                                'host': 1, 'channel': 0,
                                'id': 0, 'lun': 1}]}
        get_device_info_mock.side_effect = devices['devices']
        get_scsi_wwn_mock.return_value = wwn

        location = '10.0.2.15:3260'
        name = 'volume-00000001'
        vol = {'id': 1, 'name': name}
        initiator_wwn = ['1234567890123456', '1234567890123457']

        find_mp_device_path_mock.return_value = '/dev/mapper/mpatha'

        connection_info = self.fibrechan_connection(vol, location,
                                                    initiator_wwn)
        if side_effect and not ignore_exc:
            self.assertRaises(exception.ExceptionChainer,
                              self.connector.disconnect_volume,
                              connection_info['data'], devices['devices'][0],
                              force=True, ignore_errors=ignore_exc)
        else:
            self.connector.disconnect_volume(connection_info['data'],
                                             devices['devices'][0],
                                             force=True,
                                             ignore_errors=ignore_exc)

        flush_mock.assert_called_once_with(
            find_mp_device_path_mock.return_value)

        expected = [
            mock.call(f'/dev/disk/by-path/pci-0000:05:00.2-fc-0x{wwn}-lun-1')
            for wwn in initiator_wwn]
        if side_effect:
            del_map_mock.assert_called_once_with('dm-1')
            expected.append(mock.call(find_mp_device_path_mock.return_value))
        else:
            del_map_mock.assert_not_called()

        remove_mock.assert_called_once_with(connection_info['data'],
                                            devices['devices'],
                                            devices['devices'][0],
                                            True, mock.ANY)
        self.assertEqual(len(expected), realpath_mock.call_count)
        realpath_mock.assert_has_calls(expected)

    @ddt.data(None, mock.sentinel.addressing_mode)
    @mock.patch.object(fibre_channel.FibreChannelConnector,
                       '_get_host_devices')
    @mock.patch.object(fibre_channel.FibreChannelConnector,
                       '_get_possible_devices')
    def test__get_possible_volume_paths(self, addressing_mode,
                                        pos_devs_mock, host_devs_mock):
        conn_props = {'targets': mock.sentinel.targets}
        if addressing_mode:
            conn_props['addressing_mode'] = addressing_mode
        res = self.connector._get_possible_volume_paths(conn_props,
                                                        mock.sentinel.hbas)
        pos_devs_mock.assert_called_once_with(mock.sentinel.hbas,
                                              mock.sentinel.targets,
                                              addressing_mode)
        host_devs_mock.assert_called_once_with(pos_devs_mock.return_value)
        self.assertEqual(host_devs_mock.return_value, res)

    @mock.patch.object(linuxscsi.LinuxSCSI, 'lun_for_addressing')
    @mock.patch.object(fibre_channel.FibreChannelConnector, '_get_pci_num')
    def test__get_possible_devices(self, pci_mock, lun_mock):
        pci_mock.side_effect = [
            (mock.sentinel.platform1, mock.sentinel.pci_num1),
            (mock.sentinel.platform2, mock.sentinel.pci_num2)]
        hbas = [mock.sentinel.hba1, mock.sentinel.hba2]
        lun_mock.side_effect = [mock.sentinel.lun1B, mock.sentinel.lun2B] * 2
        targets = [('wwn1', mock.sentinel.lun1), ('wwn2', mock.sentinel.lun2)]

        res = self.connector._get_possible_devices(
            hbas, targets, mock.sentinel.addressing_mode)

        self.assertEqual(2, pci_mock.call_count)
        pci_mock.assert_has_calls([mock.call(mock.sentinel.hba1),
                                   mock.call(mock.sentinel.hba2)])
        self.assertEqual(4, lun_mock.call_count)
        lun_mock.assert_has_calls([
            mock.call(mock.sentinel.lun1, mock.sentinel.addressing_mode),
            mock.call(mock.sentinel.lun2, mock.sentinel.addressing_mode)] * 2)
        expected = [
            (mock.sentinel.platform1, mock.sentinel.pci_num1, '0xwwn1',
             mock.sentinel.lun1B),
            (mock.sentinel.platform1, mock.sentinel.pci_num1, '0xwwn2',
             mock.sentinel.lun2B),
            (mock.sentinel.platform2, mock.sentinel.pci_num2, '0xwwn1',
             mock.sentinel.lun1B),
            (mock.sentinel.platform2, mock.sentinel.pci_num2, '0xwwn2',
             mock.sentinel.lun2B),
        ]
        self.assertEqual(expected, res)
