1# Copyright 2016 Cloudbase Solutions Srl
2# All Rights Reserved.
3#
4#    Licensed under the Apache License, Version 2.0 (the "License"); you may
5#    not use this file except in compliance with the License. You may obtain
6#    a copy of the License at
7#
8#         http://www.apache.org/licenses/LICENSE-2.0
9#
10#    Unless required by applicable law or agreed to in writing, software
11#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13#    License for the specific language governing permissions and limitations
14#    under the License.
15
16from unittest import mock
17
18import ddt
19from os_win import exceptions as os_win_exc
20
21from os_brick import exception
22from os_brick.initiator.windows import iscsi
23from os_brick.tests.windows import test_base
24
25
26@ddt.ddt
27class WindowsISCSIConnectorTestCase(test_base.WindowsConnectorTestBase):
28    @mock.patch.object(iscsi.WindowsISCSIConnector, 'validate_initiators')
29    def setUp(self, mock_validate_connectors):
30        super(WindowsISCSIConnectorTestCase, self).setUp()
31
32        self._diskutils = mock.Mock()
33        self._iscsi_utils = mock.Mock()
34
35        self._connector = iscsi.WindowsISCSIConnector(
36            device_scan_interval=mock.sentinel.rescan_interval)
37        self._connector._diskutils = self._diskutils
38        self._connector._iscsi_utils = self._iscsi_utils
39
40    @ddt.data({'requested_initiators': [mock.sentinel.initiator_0],
41               'available_initiators': [mock.sentinel.initiator_0,
42                                        mock.sentinel.initiator_1]},
43              {'requested_initiators': [mock.sentinel.initiator_0],
44               'available_initiators': [mock.sentinel.initiator_1]},
45              {'requested_initiators': [],
46               'available_initiators': [mock.sentinel.software_initiator]})
47    @ddt.unpack
48    def test_validate_initiators(self, requested_initiators,
49                                 available_initiators):
50        self._iscsi_utils.get_iscsi_initiators.return_value = (
51            available_initiators)
52        self._connector.initiator_list = requested_initiators
53
54        expected_valid_initiator = not (
55            set(requested_initiators).difference(set(available_initiators)))
56        valid_initiator = self._connector.validate_initiators()
57
58        self.assertEqual(expected_valid_initiator, valid_initiator)
59
60    def test_get_initiator(self):
61        initiator = self._connector.get_initiator()
62        self.assertEqual(self._iscsi_utils.get_iscsi_initiator.return_value,
63                         initiator)
64
65    @mock.patch.object(iscsi, 'utilsfactory')
66    def test_get_connector_properties(self, mock_utilsfactory):
67        mock_iscsi_utils = (
68            mock_utilsfactory.get_iscsi_initiator_utils.return_value)
69
70        props = self._connector.get_connector_properties()
71        expected_props = dict(
72            initiator=mock_iscsi_utils.get_iscsi_initiator.return_value)
73
74        self.assertEqual(expected_props, props)
75
76    @mock.patch.object(iscsi.WindowsISCSIConnector, '_get_all_targets')
77    def test_get_all_paths(self, mock_get_all_targets):
78        initiators = [mock.sentinel.initiator_0, mock.sentinel.initiator_1]
79        all_targets = [(mock.sentinel.portal_0, mock.sentinel.target_0,
80                        mock.sentinel.lun_0),
81                       (mock.sentinel.portal_1, mock.sentinel.target_1,
82                        mock.sentinel.lun_1)]
83
84        self._connector.initiator_list = initiators
85        mock_get_all_targets.return_value = all_targets
86
87        expected_paths = [
88            (initiator_name, target_portal, target_iqn, target_lun)
89            for target_portal, target_iqn, target_lun in all_targets
90            for initiator_name in initiators]
91        all_paths = self._connector._get_all_paths(mock.sentinel.conn_props)
92
93        self.assertEqual(expected_paths, all_paths)
94        mock_get_all_targets.assert_called_once_with(mock.sentinel.conn_props)
95
96    @ddt.data(True, False)
97    @mock.patch.object(iscsi.WindowsISCSIConnector, '_get_scsi_wwn')
98    @mock.patch.object(iscsi.WindowsISCSIConnector, '_get_all_paths')
99    def test_connect_volume(self, use_multipath,
100                            mock_get_all_paths, mock_get_scsi_wwn):
101        fake_paths = [(mock.sentinel.initiator_name,
102                       mock.sentinel.target_portal,
103                       mock.sentinel.target_iqn,
104                       mock.sentinel.target_lun)] * 3
105        fake_conn_props = dict(auth_username=mock.sentinel.auth_username,
106                               auth_password=mock.sentinel.auth_password)
107
108        mock_get_all_paths.return_value = fake_paths
109        self._iscsi_utils.login_storage_target.side_effect = [
110            os_win_exc.OSWinException, None, None]
111        self._iscsi_utils.get_device_number_and_path.return_value = (
112            mock.sentinel.device_number, mock.sentinel.device_path)
113        self._connector.use_multipath = use_multipath
114
115        device_info = self._connector.connect_volume(fake_conn_props)
116        expected_device_info = dict(type='block',
117                                    path=mock.sentinel.device_path,
118                                    number=mock.sentinel.device_number,
119                                    scsi_wwn=mock_get_scsi_wwn.return_value)
120
121        self.assertEqual(expected_device_info, device_info)
122
123        mock_get_all_paths.assert_called_once_with(fake_conn_props)
124        expected_login_attempts = 3 if use_multipath else 2
125        self._iscsi_utils.login_storage_target.assert_has_calls(
126            [mock.call(target_lun=mock.sentinel.target_lun,
127                       target_iqn=mock.sentinel.target_iqn,
128                       target_portal=mock.sentinel.target_portal,
129                       auth_username=mock.sentinel.auth_username,
130                       auth_password=mock.sentinel.auth_password,
131                       mpio_enabled=use_multipath,
132                       initiator_name=mock.sentinel.initiator_name,
133                       ensure_lun_available=False)] *
134            expected_login_attempts)
135        self._iscsi_utils.get_device_number_and_path.assert_called_once_with(
136            mock.sentinel.target_iqn, mock.sentinel.target_lun,
137            retry_attempts=self._connector.device_scan_attempts,
138            retry_interval=self._connector.device_scan_interval,
139            rescan_disks=True,
140            ensure_mpio_claimed=use_multipath)
141        mock_get_scsi_wwn.assert_called_once_with(mock.sentinel.device_number)
142
143    @mock.patch.object(iscsi.WindowsISCSIConnector, '_get_all_paths')
144    def test_connect_volume_exc(self, mock_get_all_paths):
145        fake_paths = [(mock.sentinel.initiator_name,
146                       mock.sentinel.target_portal,
147                       mock.sentinel.target_iqn,
148                       mock.sentinel.target_lun)] * 3
149
150        mock_get_all_paths.return_value = fake_paths
151        self._iscsi_utils.login_storage_target.side_effect = (
152            os_win_exc.OSWinException)
153        self._connector.use_multipath = True
154
155        self.assertRaises(exception.BrickException,
156                          self._connector.connect_volume,
157                          connection_properties={})
158
159    @mock.patch.object(iscsi.WindowsISCSIConnector, '_get_all_targets')
160    def test_disconnect_volume(self, mock_get_all_targets):
161        targets = [
162            (mock.sentinel.portal_0, mock.sentinel.tg_0, mock.sentinel.lun_0),
163            (mock.sentinel.portal_1, mock.sentinel.tg_1, mock.sentinel.lun_1)]
164
165        mock_get_all_targets.return_value = targets
166        self._iscsi_utils.get_target_luns.return_value = [mock.sentinel.lun_0]
167
168        self._connector.disconnect_volume(mock.sentinel.conn_props,
169                                          mock.sentinel.dev_info)
170
171        self._diskutils.rescan_disks.assert_called_once_with()
172        mock_get_all_targets.assert_called_once_with(mock.sentinel.conn_props)
173        self._iscsi_utils.logout_storage_target.assert_called_once_with(
174            mock.sentinel.tg_0)
175        self._iscsi_utils.get_target_luns.assert_has_calls(
176            [mock.call(mock.sentinel.tg_0), mock.call(mock.sentinel.tg_1)])
177
178    @mock.patch.object(iscsi.WindowsISCSIConnector, '_get_all_targets')
179    @mock.patch.object(iscsi.WindowsISCSIConnector, '_check_device_paths')
180    def test_get_volume_paths(self, mock_check_dev_paths,
181                              mock_get_all_targets):
182        targets = [
183            (mock.sentinel.portal_0, mock.sentinel.tg_0, mock.sentinel.lun_0),
184            (mock.sentinel.portal_1, mock.sentinel.tg_1, mock.sentinel.lun_1)]
185
186        mock_get_all_targets.return_value = targets
187        self._iscsi_utils.get_device_number_and_path.return_value = [
188            mock.sentinel.dev_num, mock.sentinel.dev_path]
189
190        volume_paths = self._connector.get_volume_paths(
191            mock.sentinel.conn_props)
192        expected_paths = [mock.sentinel.dev_path]
193
194        self.assertEqual(expected_paths, volume_paths)
195        mock_check_dev_paths.assert_called_once_with(set(expected_paths))
196