# Copyright 2016 Cloudbase Solutions Srl
#
# All Rights Reserved.
#
#    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 collections
import ctypes
from unittest import mock

import ddt
import six

from os_win import _utils
from os_win import constants
from os_win import exceptions
from os_win.tests.unit import test_base
from os_win.utils.storage.initiator import iscsi_utils
from os_win.utils.winapi import constants as w_const
from os_win.utils.winapi.errmsg import iscsierr
from os_win.utils.winapi.libs import iscsidsc as iscsi_struct


@ddt.ddt
class ISCSIInitiatorUtilsTestCase(test_base.OsWinBaseTestCase):
    """Unit tests for the Hyper-V ISCSIInitiatorUtils class."""

    _autospec_classes = [
        iscsi_utils.win32utils.Win32Utils,
        iscsi_utils.diskutils.DiskUtils,
    ]

    def setUp(self):
        super(ISCSIInitiatorUtilsTestCase, self).setUp()

        self._initiator = iscsi_utils.ISCSIInitiatorUtils()
        self._diskutils = self._initiator._diskutils

        self._iscsidsc = mock.patch.object(
            iscsi_utils, 'iscsidsc', create=True).start()

        self._run_mocker = mock.patch.object(self._initiator,
                                             '_run_and_check_output')
        self._mock_run = self._run_mocker.start()

        iscsi_utils.portal_map = collections.defaultdict(set)

    def _mock_ctypes(self):
        self._ctypes = mock.Mock()
        # This is used in order to easily make assertions on the variables
        # passed by reference.
        self._ctypes.byref = lambda x: (x, "byref")

        mock.patch.object(iscsi_utils, 'ctypes', self._ctypes).start()

    def _get_fake_iscsi_utils_getter_func(self, func_side_effect,
                                          decorator_args,
                                          returned_element_count=None,
                                          required_buff_sz=None):
        @iscsi_utils.ensure_buff_and_retrieve_items(**decorator_args)
        def fake_func(inst, buff=None, buff_size=None,
                      element_count=None, *args, **kwargs):
            raised_exc = None
            try:
                # Those arguments will always be ULONGs, as requested
                # by the iscsidsc functions.
                self.assertIsInstance(buff_size, ctypes.c_ulong)
                self.assertIsInstance(element_count, ctypes.c_ulong)
                func_side_effect(buff=buff, buff_size_val=buff_size.value,
                                 element_count_val=element_count.value,
                                 *args, **kwargs)
            except Exception as ex:
                raised_exc = ex

            if returned_element_count:
                element_count.value = returned_element_count
            if required_buff_sz:
                buff_size.value = required_buff_sz

            if raised_exc:
                raise raised_exc
            return mock.sentinel.ret_val
        return fake_func

    @mock.patch.object(iscsi_utils, '_get_items_from_buff')
    def _test_ensure_buff_decorator(self, mock_get_items,
                                    required_buff_sz=None,
                                    returned_element_count=None,
                                    parse_output=False):
        insufficient_buff_exc = exceptions.Win32Exception(
            message='fake_err_msg',
            error_code=w_const.ERROR_INSUFFICIENT_BUFFER)
        func_requests_buff_sz = required_buff_sz is not None
        struct_type = ctypes.c_uint

        decorator_args = dict(struct_type=struct_type,
                              parse_output=parse_output,
                              func_requests_buff_sz=func_requests_buff_sz)

        func_side_effect = mock.Mock(side_effect=(insufficient_buff_exc, None))
        fake_func = self._get_fake_iscsi_utils_getter_func(
            returned_element_count=returned_element_count,
            required_buff_sz=required_buff_sz,
            func_side_effect=func_side_effect,
            decorator_args=decorator_args)

        ret_val = fake_func(self._initiator, fake_arg=mock.sentinel.arg)
        if parse_output:
            self.assertEqual(mock_get_items.return_value, ret_val)
        else:
            self.assertEqual(mock.sentinel.ret_val, ret_val)

        # We expect our decorated method to be called exactly two times.
        first_call_args_dict = func_side_effect.call_args_list[0][1]
        self.assertIsInstance(first_call_args_dict['buff'],
                              ctypes.POINTER(struct_type))
        self.assertEqual(first_call_args_dict['buff_size_val'], 0)
        self.assertEqual(first_call_args_dict['element_count_val'], 0)

        second_call_args_dict = func_side_effect.call_args_list[1][1]
        self.assertIsInstance(second_call_args_dict['buff'],
                              ctypes.POINTER(struct_type))
        self.assertEqual(second_call_args_dict['buff_size_val'],
                         required_buff_sz or 0)
        self.assertEqual(second_call_args_dict['element_count_val'],
                         returned_element_count or 0)

    def test_ensure_buff_func_requests_buff_sz(self):
        self._test_ensure_buff_decorator(required_buff_sz=10,
                                         parse_output=True)

    def test_ensure_buff_func_requests_el_count(self):
        self._test_ensure_buff_decorator(returned_element_count=5)

    def test_ensure_buff_func_unexpected_exception(self):
        fake_exc = exceptions.Win32Exception(message='fake_message',
                                             error_code=1)

        func_side_effect = mock.Mock(side_effect=fake_exc)
        fake_func = self._get_fake_iscsi_utils_getter_func(
            func_side_effect=func_side_effect,
            decorator_args={'struct_type': ctypes.c_ubyte})

        self.assertRaises(exceptions.Win32Exception, fake_func,
                          self._initiator)

    def test_get_items_from_buff(self):
        fake_buff_contents = 'fake_buff_contents'
        fake_buff = (ctypes.c_wchar * len(fake_buff_contents))()
        fake_buff.value = fake_buff_contents

        fake_buff = ctypes.cast(fake_buff, ctypes.POINTER(ctypes.c_ubyte))

        result = iscsi_utils._get_items_from_buff(fake_buff, ctypes.c_wchar,
                                                  len(fake_buff_contents))

        self.assertEqual(fake_buff_contents, result.value)

    def test_run_and_check_output(self):
        self._run_mocker.stop()
        self._initiator._win32utils = mock.Mock()
        mock_win32utils_run_and_check_output = (
            self._initiator._win32utils.run_and_check_output)

        self._initiator._run_and_check_output(mock.sentinel.func,
                                              mock.sentinel.arg,
                                              fake_kwarg=mock.sentinel.kwarg)

        mock_win32utils_run_and_check_output.assert_called_once_with(
            mock.sentinel.func,
            mock.sentinel.arg,
            fake_kwarg=mock.sentinel.kwarg,
            error_msg_src=iscsierr.err_msg_dict,
            failure_exc=exceptions.ISCSIInitiatorAPIException)

    def test_get_iscsi_persistent_logins(self):
        self._mock_ctypes()

        _get_iscsi_persistent_logins = _utils.get_wrapped_function(
            self._initiator._get_iscsi_persistent_logins)
        _get_iscsi_persistent_logins(
            self._initiator,
            buff=mock.sentinel.buff,
            buff_size=mock.sentinel.buff_size,
            element_count=mock.sentinel.element_count)

        self._mock_run.assert_called_once_with(
            self._iscsidsc.ReportIScsiPersistentLoginsW,
            self._ctypes.byref(mock.sentinel.element_count),
            mock.sentinel.buff,
            self._ctypes.byref(mock.sentinel.buff_size))

    @mock.patch.object(iscsi_utils.ISCSIInitiatorUtils,
                       '_parse_string_list')
    def test_get_targets(self, mock_parse_string_list):
        self._mock_ctypes()

        get_targets = _utils.get_wrapped_function(
            self._initiator.get_targets)
        mock_el_count = mock.Mock(value=mock.sentinel.element_count)

        resulted_target_list = get_targets(
            self._initiator,
            forced_update=mock.sentinel.forced_update,
            element_count=mock_el_count,
            buff=mock.sentinel.buff)
        self.assertEqual(mock_parse_string_list.return_value,
                         resulted_target_list)

        self._mock_run.assert_called_once_with(
            self._iscsidsc.ReportIScsiTargetsW,
            mock.sentinel.forced_update,
            self._ctypes.byref(mock_el_count),
            mock.sentinel.buff)
        mock_parse_string_list.assert_called_once_with(
            mock.sentinel.buff, mock.sentinel.element_count)

    @mock.patch.object(iscsi_utils.ISCSIInitiatorUtils,
                       '_parse_string_list')
    def test_get_initiators(self, mock_parse_string_list):
        self._mock_ctypes()

        get_initiators = _utils.get_wrapped_function(
            self._initiator.get_iscsi_initiators)
        mock_el_count = mock.Mock(value=mock.sentinel.element_count)

        resulted_initator_list = get_initiators(
            self._initiator,
            element_count=mock_el_count,
            buff=mock.sentinel.buff)
        self.assertEqual(mock_parse_string_list.return_value,
                         resulted_initator_list)

        self._mock_run.assert_called_once_with(
            self._iscsidsc.ReportIScsiInitiatorListW,
            self._ctypes.byref(mock_el_count),
            mock.sentinel.buff)
        mock_parse_string_list.assert_called_once_with(
            mock.sentinel.buff, mock.sentinel.element_count)

    def test_parse_string_list(self):
        self._mock_ctypes()

        fake_buff = 'fake\x00buff\x00\x00'
        self._ctypes.cast.return_value = fake_buff

        str_list = self._initiator._parse_string_list(fake_buff,
                                                      len(fake_buff))

        self.assertEqual(['fake', 'buff'], str_list)

        self._ctypes.cast.assert_called_once_with(
            fake_buff, self._ctypes.POINTER.return_value)
        self._ctypes.POINTER.assert_called_once_with(self._ctypes.c_wchar)

    def test_get_iscsi_initiator(self):
        self._mock_ctypes()

        self._ctypes.c_wchar = mock.MagicMock()
        fake_buff = (self._ctypes.c_wchar * (
            w_const.MAX_ISCSI_NAME_LEN + 1))()
        fake_buff.value = mock.sentinel.buff_value

        resulted_iscsi_initiator = self._initiator.get_iscsi_initiator()

        self._mock_run.assert_called_once_with(
            self._iscsidsc.GetIScsiInitiatorNodeNameW,
            fake_buff)
        self.assertEqual(mock.sentinel.buff_value,
                         resulted_iscsi_initiator)

    @mock.patch('socket.getfqdn')
    def test_get_iscsi_initiator_exception(self, mock_get_fqdn):
        fake_fqdn = 'fakehost.FAKE-DOMAIN.com'
        fake_exc = exceptions.ISCSIInitiatorAPIException(
            message='fake_message',
            error_code=1,
            func_name='fake_func')

        self._mock_run.side_effect = fake_exc
        mock_get_fqdn.return_value = fake_fqdn

        resulted_iqn = self._initiator.get_iscsi_initiator()

        expected_iqn = "%s:%s" % (self._initiator._MS_IQN_PREFIX,
                                  fake_fqdn.lower())
        self.assertEqual(expected_iqn, resulted_iqn)

    @mock.patch.object(ctypes, 'byref')
    @mock.patch.object(iscsi_struct, 'ISCSI_UNIQUE_CONNECTION_ID')
    @mock.patch.object(iscsi_struct, 'ISCSI_UNIQUE_SESSION_ID')
    def test_login_iscsi_target(self, mock_cls_ISCSI_UNIQUE_SESSION_ID,
                                mock_cls_ISCSI_UNIQUE_CONNECTION_ID,
                                mock_byref):
        fake_target_name = 'fake_target_name'

        resulted_session_id, resulted_conection_id = (
            self._initiator._login_iscsi_target(fake_target_name))

        args_list = self._mock_run.call_args_list[0][0]

        self.assertIsInstance(args_list[1], ctypes.c_wchar_p)
        self.assertEqual(fake_target_name, args_list[1].value)
        self.assertIsInstance(args_list[4], ctypes.c_ulong)
        self.assertEqual(
            ctypes.c_ulong(w_const.ISCSI_ANY_INITIATOR_PORT).value,
            args_list[4].value)
        self.assertIsInstance(args_list[6], ctypes.c_ulonglong)
        self.assertEqual(0, args_list[6].value)
        self.assertIsInstance(args_list[9], ctypes.c_ulong)
        self.assertEqual(0, args_list[9].value)

        mock_byref.assert_has_calls([
            mock.call(mock_cls_ISCSI_UNIQUE_SESSION_ID.return_value),
            mock.call(mock_cls_ISCSI_UNIQUE_CONNECTION_ID.return_value)])
        self.assertEqual(
            mock_cls_ISCSI_UNIQUE_SESSION_ID.return_value,
            resulted_session_id)
        self.assertEqual(
            mock_cls_ISCSI_UNIQUE_CONNECTION_ID.return_value,
            resulted_conection_id)

    def test_get_iscsi_sessions(self):
        self._mock_ctypes()

        _get_iscsi_sessions = _utils.get_wrapped_function(
            self._initiator._get_iscsi_sessions)
        _get_iscsi_sessions(
            self._initiator,
            buff=mock.sentinel.buff,
            buff_size=mock.sentinel.buff_size,
            element_count=mock.sentinel.element_count)

        self._mock_run.assert_called_once_with(
            self._iscsidsc.GetIScsiSessionListW,
            self._ctypes.byref(mock.sentinel.buff_size),
            self._ctypes.byref(mock.sentinel.element_count),
            mock.sentinel.buff)

    @mock.patch.object(iscsi_utils.ISCSIInitiatorUtils,
                       '_get_iscsi_sessions')
    def test_get_iscsi_target_sessions(self, mock_get_iscsi_sessions,
                                       target_sessions_found=True):
        fake_session = mock.Mock(TargetNodeName="FAKE_TARGET_NAME",
                                 ConnectionCount=1)
        fake_disconn_session = mock.Mock(
            TargetNodeName="fake_target_name",
            ConnectionCount=0)
        other_session = mock.Mock(TargetNodeName="other_target_name",
                                  ConnectionCount=1)

        sessions = [fake_session, fake_disconn_session, other_session]
        mock_get_iscsi_sessions.return_value = sessions

        resulted_tgt_sessions = self._initiator._get_iscsi_target_sessions(
            "fake_target_name")

        self.assertEqual([fake_session], resulted_tgt_sessions)

    def test_get_iscsi_session_devices(self):
        self._mock_ctypes()

        _get_iscsi_session_devices = _utils.get_wrapped_function(
            self._initiator._get_iscsi_session_devices)
        _get_iscsi_session_devices(
            self._initiator,
            mock.sentinel.session_id,
            buff=mock.sentinel.buff,
            element_count=mock.sentinel.element_count)

        self._mock_run.assert_called_once_with(
            self._iscsidsc.GetDevicesForIScsiSessionW,
            self._ctypes.byref(mock.sentinel.session_id),
            self._ctypes.byref(mock.sentinel.element_count),
            mock.sentinel.buff)

    @mock.patch.object(iscsi_utils.ISCSIInitiatorUtils,
                       '_get_iscsi_session_devices')
    def test_get_iscsi_session_luns(self, mock_get_iscsi_session_devices):
        fake_device = mock.Mock()
        fake_device.StorageDeviceNumber.DeviceType = w_const.FILE_DEVICE_DISK
        mock_get_iscsi_session_devices.return_value = [fake_device,
                                                       mock.Mock()]

        resulted_luns = self._initiator._get_iscsi_session_disk_luns(
            mock.sentinel.session_id)
        expected_luns = [fake_device.ScsiAddress.Lun]

        mock_get_iscsi_session_devices.assert_called_once_with(
            mock.sentinel.session_id)
        self.assertEqual(expected_luns, resulted_luns)

    @mock.patch.object(iscsi_utils.ISCSIInitiatorUtils,
                       '_get_iscsi_session_devices')
    def test_get_iscsi_device_from_session(self,
                                           mock_get_iscsi_session_devices):
        fake_device = mock.Mock()
        fake_device.ScsiAddress.Lun = mock.sentinel.target_lun
        mock_get_iscsi_session_devices.return_value = [mock.Mock(),
                                                       fake_device]

        resulted_device = self._initiator._get_iscsi_device_from_session(
            mock.sentinel.session_id,
            mock.sentinel.target_lun)

        mock_get_iscsi_session_devices.assert_called_once_with(
            mock.sentinel.session_id)
        self.assertEqual(fake_device, resulted_device)

    @mock.patch.object(iscsi_utils.ISCSIInitiatorUtils,
                       'get_device_number_and_path')
    def test_get_device_number_for_target(self, mock_get_dev_num_and_path):
        dev_num = self._initiator.get_device_number_for_target(
            mock.sentinel.target_name, mock.sentinel.lun,
            mock.sentinel.fail_if_not_found)

        mock_get_dev_num_and_path.assert_called_once_with(
            mock.sentinel.target_name, mock.sentinel.lun,
            mock.sentinel.fail_if_not_found)
        self.assertEqual(mock_get_dev_num_and_path.return_value[0], dev_num)

    @mock.patch.object(iscsi_utils.ISCSIInitiatorUtils,
                       'ensure_lun_available')
    def test_get_device_number_and_path(self, mock_ensure_lun_available):
        mock_ensure_lun_available.return_value = (mock.sentinel.dev_num,
                                                  mock.sentinel.dev_path)

        dev_num, dev_path = self._initiator.get_device_number_and_path(
            mock.sentinel.target_name, mock.sentinel.lun,
            retry_attempts=mock.sentinel.retry_attempts,
            retry_interval=mock.sentinel.retry_interval,
            rescan_disks=mock.sentinel.rescan_disks,
            ensure_mpio_claimed=mock.sentinel.ensure_mpio_claimed)

        mock_ensure_lun_available.assert_called_once_with(
            mock.sentinel.target_name, mock.sentinel.lun,
            rescan_attempts=mock.sentinel.retry_attempts,
            retry_interval=mock.sentinel.retry_interval,
            rescan_disks=mock.sentinel.rescan_disks,
            ensure_mpio_claimed=mock.sentinel.ensure_mpio_claimed)

        self.assertEqual(mock.sentinel.dev_num, dev_num)
        self.assertEqual(mock.sentinel.dev_path, dev_path)

    @ddt.data(True, False)
    @mock.patch.object(iscsi_utils.ISCSIInitiatorUtils,
                       'ensure_lun_available')
    def test_get_device_number_and_path_exc(self, fail_if_not_found,
                                            mock_ensure_lun_available):
        raised_exc = exceptions.ISCSILunNotAvailable
        mock_ensure_lun_available.side_effect = raised_exc(
            target_iqn=mock.sentinel.target_iqn,
            target_lun=mock.sentinel.target_lun)

        if fail_if_not_found:
            self.assertRaises(raised_exc,
                              self._initiator.get_device_number_and_path,
                              mock.sentinel.target_name,
                              mock.sentinel.lun,
                              fail_if_not_found)
        else:
            dev_num, dev_path = self._initiator.get_device_number_and_path(
                mock.sentinel.target_name,
                mock.sentinel.lun,
                fail_if_not_found)
            self.assertIsNone(dev_num)
            self.assertIsNone(dev_path)

    @mock.patch.object(iscsi_utils.ISCSIInitiatorUtils,
                       '_get_iscsi_target_sessions')
    @mock.patch.object(iscsi_utils.ISCSIInitiatorUtils,
                       '_get_iscsi_session_disk_luns')
    def test_get_target_luns(self, mock_get_iscsi_session_disk_luns,
                             mock_get_iscsi_target_sessions):
        fake_session = mock.Mock()
        mock_get_iscsi_target_sessions.return_value = [fake_session]

        retrieved_luns = [mock.sentinel.lun_0]
        mock_get_iscsi_session_disk_luns.return_value = retrieved_luns

        resulted_luns = self._initiator.get_target_luns(
            mock.sentinel.target_name)

        mock_get_iscsi_target_sessions.assert_called_once_with(
            mock.sentinel.target_name)
        mock_get_iscsi_session_disk_luns.assert_called_once_with(
            fake_session.SessionId)
        self.assertEqual(retrieved_luns, resulted_luns)

    @mock.patch.object(iscsi_utils.ISCSIInitiatorUtils,
                       'get_target_luns')
    def test_get_target_lun_count(self, mock_get_target_luns):
        target_luns = [mock.sentinel.lun0, mock.sentinel.lun1]
        mock_get_target_luns.return_value = target_luns

        lun_count = self._initiator.get_target_lun_count(
            mock.sentinel.target_name)

        self.assertEqual(len(target_luns), lun_count)
        mock_get_target_luns.assert_called_once_with(
            mock.sentinel.target_name)

    def test_logout_iscsi_target(self):
        self._mock_ctypes()

        self._initiator._logout_iscsi_target(mock.sentinel.session_id)

        self._mock_run.assert_called_once_with(
            self._iscsidsc.LogoutIScsiTarget,
            self._ctypes.byref(mock.sentinel.session_id))

    def test_add_static_target(self):
        self._mock_ctypes()

        is_persistent = True
        self._initiator._add_static_target(mock.sentinel.target_name,
                                           is_persistent=is_persistent)

        self._mock_run.assert_called_once_with(
            self._iscsidsc.AddIScsiStaticTargetW,
            self._ctypes.c_wchar_p(mock.sentinel.target_name),
            None, 0, is_persistent, None, None, None)

    def test_remove_static_target(self):
        self._mock_ctypes()

        self._initiator._remove_static_target(mock.sentinel.target_name)

        expected_ignored_err_codes = [w_const.ISDSC_TARGET_NOT_FOUND]
        self._mock_run.assert_called_once_with(
            self._iscsidsc.RemoveIScsiStaticTargetW,
            self._ctypes.c_wchar_p(mock.sentinel.target_name),
            ignored_error_codes=expected_ignored_err_codes)

    def test_get_login_opts(self):
        fake_username = 'fake_chap_username'
        fake_password = 'fake_chap_secret'
        auth_type = constants.ISCSI_CHAP_AUTH_TYPE
        login_flags = w_const.ISCSI_LOGIN_FLAG_MULTIPATH_ENABLED

        login_opts = self._initiator._get_login_opts(
            auth_username=fake_username,
            auth_password=fake_password,
            auth_type=auth_type,
            login_flags=login_flags)

        self.assertEqual(len(fake_username), login_opts.UsernameLength)
        self.assertEqual(len(fake_password), login_opts.PasswordLength)

        username_struct_contents = ctypes.cast(
            login_opts.Username,
            ctypes.POINTER(ctypes.c_char * len(fake_username))).contents.value
        pwd_struct_contents = ctypes.cast(
            login_opts.Password,
            ctypes.POINTER(ctypes.c_char * len(fake_password))).contents.value

        self.assertEqual(six.b(fake_username), username_struct_contents)
        self.assertEqual(six.b(fake_password), pwd_struct_contents)

        expected_info_bitmap = (w_const.ISCSI_LOGIN_OPTIONS_USERNAME |
                                w_const.ISCSI_LOGIN_OPTIONS_PASSWORD |
                                w_const.ISCSI_LOGIN_OPTIONS_AUTH_TYPE)
        self.assertEqual(expected_info_bitmap,
                         login_opts.InformationSpecified)
        self.assertEqual(login_flags,
                         login_opts.LoginFlags)

    @mock.patch.object(iscsi_utils.ISCSIInitiatorUtils,
                       '_get_iscsi_session_devices')
    def test_session_on_path_exists(self, mock_get_iscsi_session_devices):
        mock_device = mock.Mock(InitiatorName=mock.sentinel.initiator_name)
        mock_get_iscsi_session_devices.return_value = [mock_device]

        fake_connection = mock.Mock(TargetAddress=mock.sentinel.portal_addr,
                                    TargetSocket=mock.sentinel.portal_port)
        fake_connections = [mock.Mock(), fake_connection]
        fake_session = mock.Mock(ConnectionCount=len(fake_connections),
                                 Connections=fake_connections)
        fake_sessions = [mock.Mock(Connections=[], ConnectionCount=0),
                         fake_session]

        session_on_path_exists = self._initiator._session_on_path_exists(
            fake_sessions, mock.sentinel.portal_addr,
            mock.sentinel.portal_port,
            mock.sentinel.initiator_name)
        self.assertTrue(session_on_path_exists)
        mock_get_iscsi_session_devices.assert_has_calls(
            [mock.call(session.SessionId) for session in fake_sessions])

    @mock.patch.object(iscsi_utils.ISCSIInitiatorUtils,
                       '_get_iscsi_target_sessions')
    @mock.patch.object(iscsi_utils.ISCSIInitiatorUtils,
                       '_session_on_path_exists')
    def _test_new_session_required(self, mock_session_on_path_exists,
                                   mock_get_iscsi_target_sessions,
                                   sessions=None,
                                   mpio_enabled=False,
                                   session_on_path_exists=False):
        mock_get_iscsi_target_sessions.return_value = sessions
        mock_session_on_path_exists.return_value = session_on_path_exists

        expected_result = (not sessions or
                           (mpio_enabled and not session_on_path_exists))
        result = self._initiator._new_session_required(
            mock.sentinel.target_iqn,
            mock.sentinel.portal_addr,
            mock.sentinel.portal_port,
            mock.sentinel.initiator_name,
            mpio_enabled)
        self.assertEqual(expected_result, result)

        if sessions and mpio_enabled:
            mock_session_on_path_exists.assert_called_once_with(
                sessions,
                mock.sentinel.portal_addr,
                mock.sentinel.portal_port,
                mock.sentinel.initiator_name)

    def test_new_session_required_no_sessions(self):
        self._test_new_session_required()

    def test_new_session_required_existing_sessions_no_mpio(self):
        self._test_new_session_required(sessions=mock.sentinel.sessions)

    def test_new_session_required_existing_sessions_mpio_enabled(self):
        self._test_new_session_required(sessions=mock.sentinel.sessions,
                                        mpio_enabled=True)

    def test_new_session_required_session_on_path_exists(self):
        self._test_new_session_required(sessions=mock.sentinel.sessions,
                                        mpio_enabled=True,
                                        session_on_path_exists=True)

    @mock.patch.object(iscsi_utils.ISCSIInitiatorUtils,
                       '_get_login_opts')
    @mock.patch.object(iscsi_struct, 'ISCSI_TARGET_PORTAL')
    @mock.patch.object(iscsi_utils.ISCSIInitiatorUtils,
                       '_new_session_required')
    @mock.patch.object(iscsi_utils.ISCSIInitiatorUtils, 'get_targets')
    @mock.patch.object(iscsi_utils.ISCSIInitiatorUtils, '_login_iscsi_target')
    @mock.patch.object(iscsi_utils.ISCSIInitiatorUtils,
                       'ensure_lun_available')
    @mock.patch.object(iscsi_utils.ISCSIInitiatorUtils,
                       '_add_static_target')
    def _test_login_storage_target(self, mock_add_static_target,
                                   mock_ensure_lun_available,
                                   mock_login_iscsi_target,
                                   mock_get_targets,
                                   mock_session_required,
                                   mock_cls_ISCSI_TARGET_PORTAL,
                                   mock_get_login_opts,
                                   mpio_enabled=False,
                                   login_required=True):
        fake_portal_addr = '127.0.0.1'
        fake_portal_port = 3260
        fake_target_portal = '%s:%s' % (fake_portal_addr, fake_portal_port)

        fake_portal = mock_cls_ISCSI_TARGET_PORTAL.return_value
        fake_login_opts = mock_get_login_opts.return_value

        mock_get_targets.return_value = []
        mock_login_iscsi_target.return_value = (mock.sentinel.session_id,
                                                mock.sentinel.conn_id)
        mock_session_required.return_value = login_required

        self._initiator.login_storage_target(
            mock.sentinel.target_lun,
            mock.sentinel.target_iqn,
            fake_target_portal,
            auth_username=mock.sentinel.auth_username,
            auth_password=mock.sentinel.auth_password,
            auth_type=mock.sentinel.auth_type,
            mpio_enabled=mpio_enabled,
            rescan_attempts=mock.sentinel.rescan_attempts)

        mock_get_targets.assert_called_once_with()
        mock_add_static_target.assert_called_once_with(
            mock.sentinel.target_iqn)

        if login_required:
            expected_login_flags = (
                w_const.ISCSI_LOGIN_FLAG_MULTIPATH_ENABLED
                if mpio_enabled else 0)
            mock_get_login_opts.assert_called_once_with(
                mock.sentinel.auth_username,
                mock.sentinel.auth_password,
                mock.sentinel.auth_type,
                expected_login_flags)
            mock_cls_ISCSI_TARGET_PORTAL.assert_called_once_with(
                Address=fake_portal_addr,
                Socket=fake_portal_port)
            mock_login_iscsi_target.assert_has_calls([
                mock.call(mock.sentinel.target_iqn,
                          fake_portal,
                          fake_login_opts,
                          is_persistent=True),
                mock.call(mock.sentinel.target_iqn,
                          fake_portal,
                          fake_login_opts,
                          is_persistent=False)])
        else:
            self.assertFalse(mock_login_iscsi_target.called)

        mock_ensure_lun_available.assert_called_once_with(
            mock.sentinel.target_iqn,
            mock.sentinel.target_lun,
            mock.sentinel.rescan_attempts)

    def test_login_storage_target_path_exists(self):
        self._test_login_storage_target(login_required=False)

    def test_login_new_storage_target_no_mpio(self):
        self._test_login_storage_target()

    def test_login_storage_target_new_path_using_mpio(self):
        self._test_login_storage_target(mpio_enabled=True)

    @ddt.data(dict(rescan_disks=True),
              dict(retry_interval=mock.sentinel.retry_interval))
    @ddt.unpack
    @mock.patch.object(iscsi_utils.ISCSIInitiatorUtils,
                       '_get_iscsi_device_from_session')
    @mock.patch.object(iscsi_utils.ISCSIInitiatorUtils,
                       '_get_iscsi_target_sessions')
    @mock.patch('time.sleep')
    def test_ensure_lun_available(self, mock_sleep,
                                  mock_get_iscsi_target_sessions,
                                  mock_get_iscsi_device_from_session,
                                  rescan_disks=False, retry_interval=0):
        retry_count = 6
        mock_get_iscsi_target_sessions.return_value = [
            mock.Mock(SessionId=mock.sentinel.session_id)]

        fake_exc = exceptions.ISCSIInitiatorAPIException(
            message='fake_message',
            error_code=1,
            func_name='fake_func')
        dev_num_side_eff = [None, -1] + [mock.sentinel.dev_num] * 3
        dev_path_side_eff = ([mock.sentinel.dev_path] * 2 +
                             [None] + [mock.sentinel.dev_path] * 2)
        fake_device = mock.Mock()
        type(fake_device.StorageDeviceNumber).DeviceNumber = (
            mock.PropertyMock(side_effect=dev_num_side_eff))
        type(fake_device).LegacyName = (
            mock.PropertyMock(side_effect=dev_path_side_eff))

        mock_get_dev_side_eff = [None, fake_exc] + [fake_device] * 5
        mock_get_iscsi_device_from_session.side_effect = mock_get_dev_side_eff
        self._diskutils.is_mpio_disk.side_effect = [False, True]

        dev_num, dev_path = self._initiator.ensure_lun_available(
            mock.sentinel.target_iqn,
            mock.sentinel.target_lun,
            rescan_attempts=retry_count,
            retry_interval=retry_interval,
            rescan_disks=rescan_disks,
            ensure_mpio_claimed=True)

        self.assertEqual(mock.sentinel.dev_num, dev_num)
        self.assertEqual(mock.sentinel.dev_path, dev_path)

        mock_get_iscsi_target_sessions.assert_has_calls(
            [mock.call(mock.sentinel.target_iqn)] * (retry_count + 1))
        mock_get_iscsi_device_from_session.assert_has_calls(
            [mock.call(mock.sentinel.session_id,
                       mock.sentinel.target_lun)] * retry_count)
        self._diskutils.is_mpio_disk.assert_has_calls(
            [mock.call(mock.sentinel.dev_num)] * 2)

        expected_rescan_count = retry_count if rescan_disks else 0
        self.assertEqual(
            expected_rescan_count,
            self._diskutils.rescan_disks.call_count)

        if retry_interval:
            mock_sleep.assert_has_calls(
                [mock.call(retry_interval)] * retry_count)
        else:
            self.assertFalse(mock_sleep.called)

    @mock.patch.object(iscsi_utils.ISCSIInitiatorUtils,
                       '_get_iscsi_target_sessions')
    @mock.patch.object(iscsi_utils.ISCSIInitiatorUtils,
                       '_logout_iscsi_target')
    @mock.patch.object(iscsi_utils.ISCSIInitiatorUtils,
                       '_remove_target_persistent_logins')
    @mock.patch.object(iscsi_utils.ISCSIInitiatorUtils,
                       '_remove_static_target')
    def test_logout_storage_target(self, mock_remove_static_target,
                                   mock_remove_target_persistent_logins,
                                   mock_logout_iscsi_target,
                                   mock_get_iscsi_target_sessions):
        fake_session = mock.Mock(SessionId=mock.sentinel.session_id)
        mock_get_iscsi_target_sessions.return_value = [fake_session]

        self._initiator.logout_storage_target(mock.sentinel.target_iqn)

        mock_get_iscsi_target_sessions.assert_called_once_with(
            mock.sentinel.target_iqn, connected_only=False)
        mock_logout_iscsi_target.assert_called_once_with(
            mock.sentinel.session_id)
        mock_remove_target_persistent_logins.assert_called_once_with(
            mock.sentinel.target_iqn)
        mock_remove_static_target.assert_called_once_with(
            mock.sentinel.target_iqn)

    @mock.patch.object(iscsi_utils.ISCSIInitiatorUtils,
                       '_remove_persistent_login')
    @mock.patch.object(iscsi_utils.ISCSIInitiatorUtils,
                       '_get_iscsi_persistent_logins')
    def test_remove_target_persistent_logins(self,
                                             mock_get_iscsi_persistent_logins,
                                             mock_remove_persistent_login):
        fake_persistent_login = mock.Mock(TargetName=mock.sentinel.target_iqn)
        mock_get_iscsi_persistent_logins.return_value = [fake_persistent_login]

        self._initiator._remove_target_persistent_logins(
            mock.sentinel.target_iqn)

        mock_remove_persistent_login.assert_called_once_with(
            fake_persistent_login)
        mock_get_iscsi_persistent_logins.assert_called_once_with()

    @mock.patch.object(ctypes, 'byref')
    def test_remove_persistent_login(self, mock_byref):
        fake_persistent_login = mock.Mock()
        fake_persistent_login.InitiatorInstance = 'fake_initiator_instance'
        fake_persistent_login.TargetName = 'fake_target_name'

        self._initiator._remove_persistent_login(fake_persistent_login)

        args_list = self._mock_run.call_args_list[0][0]
        self.assertIsInstance(args_list[1], ctypes.c_wchar_p)
        self.assertEqual(fake_persistent_login.InitiatorInstance,
                         args_list[1].value)
        self.assertIsInstance(args_list[3], ctypes.c_wchar_p)
        self.assertEqual(fake_persistent_login.TargetName,
                         args_list[3].value)
        mock_byref.assert_called_once_with(fake_persistent_login.TargetPortal)
