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 os_win import exceptions as os_win_exc
17from os_win import utilsfactory
18from oslo_log import log as logging
19
20from os_brick import exception
21from os_brick.i18n import _
22from os_brick.initiator.connectors import base_iscsi
23from os_brick.initiator.windows import base as win_conn_base
24from os_brick import utils
25
26LOG = logging.getLogger(__name__)
27
28
29class WindowsISCSIConnector(win_conn_base.BaseWindowsConnector,
30                            base_iscsi.BaseISCSIConnector):
31    def __init__(self, *args, **kwargs):
32        super(WindowsISCSIConnector, self).__init__(*args, **kwargs)
33        self.use_multipath = kwargs.pop('use_multipath', False)
34        self.initiator_list = kwargs.pop('initiator_list', [])
35
36        self._iscsi_utils = utilsfactory.get_iscsi_initiator_utils()
37
38        self.validate_initiators()
39
40    def validate_initiators(self):
41        """Validates the list of requested initiator HBAs
42
43        Validates the list of requested initiator HBAs to be used
44        when establishing iSCSI sessions.
45        """
46        valid_initiator_list = True
47        if not self.initiator_list:
48            LOG.info("No iSCSI initiator was explicitly requested. "
49                     "The Microsoft iSCSI initiator will choose the "
50                     "initiator when establishing sessions.")
51        else:
52            available_initiators = self._iscsi_utils.get_iscsi_initiators()
53            for initiator in self.initiator_list:
54                if initiator not in available_initiators:
55                    LOG.warning("The requested initiator %(req_initiator)s "
56                                "is not in the list of available initiators: "
57                                "%(avail_initiators)s.",
58                                dict(req_initiator=initiator,
59                                     avail_initiators=available_initiators))
60                    valid_initiator_list = False
61        return valid_initiator_list
62
63    def get_initiator(self):
64        """Returns the iSCSI initiator node name."""
65        return self._iscsi_utils.get_iscsi_initiator()
66
67    @staticmethod
68    def get_connector_properties(*args, **kwargs):
69        iscsi_utils = utilsfactory.get_iscsi_initiator_utils()
70        initiator = iscsi_utils.get_iscsi_initiator()
71        return dict(initiator=initiator)
72
73    def _get_all_paths(self, connection_properties):
74        initiator_list = self.initiator_list or [None]
75        all_targets = self._get_all_targets(connection_properties)
76        paths = [(initiator_name, target_portal, target_iqn, target_lun)
77                 for target_portal, target_iqn, target_lun in all_targets
78                 for initiator_name in initiator_list]
79        return paths
80
81    @utils.trace
82    def connect_volume(self, connection_properties):
83        connected_target_mappings = set()
84        volume_connected = False
85
86        for (initiator_name,
87             target_portal,
88             target_iqn,
89             target_lun) in self._get_all_paths(connection_properties):
90            try:
91                LOG.info("Attempting to establish an iSCSI session to "
92                         "target %(target_iqn)s on portal %(target_portal)s "
93                         "accessing LUN %(target_lun)s using initiator "
94                         "%(initiator_name)s.",
95                         dict(target_portal=target_portal,
96                              target_iqn=target_iqn,
97                              target_lun=target_lun,
98                              initiator_name=initiator_name))
99                self._iscsi_utils.login_storage_target(
100                    target_lun=target_lun,
101                    target_iqn=target_iqn,
102                    target_portal=target_portal,
103                    auth_username=connection_properties.get('auth_username'),
104                    auth_password=connection_properties.get('auth_password'),
105                    mpio_enabled=self.use_multipath,
106                    initiator_name=initiator_name,
107                    ensure_lun_available=False)
108
109                connected_target_mappings.add((target_iqn, target_lun))
110
111                if not self.use_multipath:
112                    break
113            except os_win_exc.OSWinException:
114                LOG.exception("Could not establish the iSCSI session.")
115
116        for target_iqn, target_lun in connected_target_mappings:
117            try:
118                (device_number,
119                 device_path) = self._iscsi_utils.get_device_number_and_path(
120                    target_iqn, target_lun,
121                    retry_attempts=self.device_scan_attempts,
122                    retry_interval=self.device_scan_interval,
123                    rescan_disks=True,
124                    ensure_mpio_claimed=self.use_multipath)
125                volume_connected = True
126            except os_win_exc.OSWinException:
127                LOG.exception("Could not retrieve device path for target "
128                              "%(target_iqn)s and lun %(target_lun)s.",
129                              dict(target_iqn=target_iqn,
130                                   target_lun=target_lun))
131
132        if not volume_connected:
133            raise exception.BrickException(
134                _("Could not connect volume %s.") % connection_properties)
135
136        scsi_wwn = self._get_scsi_wwn(device_number)
137
138        device_info = {'type': 'block',
139                       'path': device_path,
140                       'number': device_number,
141                       'scsi_wwn': scsi_wwn}
142        return device_info
143
144    @utils.trace
145    def disconnect_volume(self, connection_properties, device_info=None,
146                          force=False, ignore_errors=False):
147        # We want to refresh the cached information first.
148        self._diskutils.rescan_disks()
149        for (target_portal,
150             target_iqn,
151             target_lun) in self._get_all_targets(connection_properties):
152
153            luns = self._iscsi_utils.get_target_luns(target_iqn)
154            # We disconnect the target only if it does not expose other
155            # luns which may be in use.
156            if not luns or luns == [target_lun]:
157                self._iscsi_utils.logout_storage_target(target_iqn)
158
159    @utils.trace
160    def get_volume_paths(self, connection_properties):
161        device_paths = set()
162
163        for (target_portal,
164             target_iqn,
165             target_lun) in self._get_all_targets(connection_properties):
166
167            (device_number,
168             device_path) = self._iscsi_utils.get_device_number_and_path(
169                target_iqn, target_lun,
170                ensure_mpio_claimed=self.use_multipath)
171            if device_path:
172                device_paths.add(device_path)
173
174        self._check_device_paths(device_paths)
175        return list(device_paths)
176