1# Copyright 2015 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 oslo_log import log as logging 17import six 18 19from os_win._i18n import _ 20from os_win import _utils 21from os_win import constants 22from os_win import exceptions 23from os_win.utils import baseutils 24from os_win.utils import hostutils 25from os_win.utils import pathutils 26from os_win.utils import win32utils 27 28LOG = logging.getLogger(__name__) 29 30 31@six.add_metaclass(baseutils.SynchronizedMeta) 32class ISCSITargetUtils(baseutils.BaseUtils): 33 ID_METHOD_DNS_NAME = 1 34 ID_METHOD_IPV4_ADDR = 2 35 ID_METHOD_MAC_ADDR = 3 36 ID_METHOD_IQN = 4 37 ID_METHOD_IPV6_ADDR = 5 38 39 _ERR_FILE_EXISTS = 80 40 41 def __init__(self): 42 self._conn_wmi = self._get_wmi_conn('//./root/wmi') 43 self._ensure_wt_provider_available() 44 45 self._pathutils = pathutils.PathUtils() 46 self._hostutils = hostutils.HostUtils() 47 self._win32utils = win32utils.Win32Utils() 48 49 self._win_gteq_6_3 = self._hostutils.check_min_windows_version(6, 3) 50 51 def _ensure_wt_provider_available(self): 52 try: 53 self._conn_wmi.WT_Portal 54 except AttributeError: 55 err_msg = _("The Windows iSCSI target provider is not available.") 56 raise exceptions.ISCSITargetException(err_msg) 57 58 def get_supported_disk_format(self): 59 return (constants.DISK_FORMAT_VHDX 60 if self._win_gteq_6_3 else constants.DISK_FORMAT_VHD) 61 62 def get_supported_vhd_type(self): 63 return (constants.VHD_TYPE_DYNAMIC 64 if self._win_gteq_6_3 else constants.VHD_TYPE_FIXED) 65 66 def get_portal_locations(self, available_only=True, 67 fail_if_none_found=True): 68 wt_portals = self._conn_wmi.WT_Portal() 69 70 if available_only: 71 wt_portals = list(filter(lambda portal: portal.Listen, wt_portals)) 72 73 if not wt_portals and fail_if_none_found: 74 err_msg = _("No valid iSCSI portal was found.") 75 raise exceptions.ISCSITargetException(err_msg) 76 77 portal_locations = [self._get_portal_location(portal) 78 for portal in wt_portals] 79 return portal_locations 80 81 def _get_portal_location(self, wt_portal): 82 return '%s:%s' % (wt_portal.Address, wt_portal.Port) 83 84 def _get_wt_host(self, target_name, fail_if_not_found=True): 85 hosts = self._conn_wmi.WT_Host(HostName=target_name) 86 87 if hosts: 88 return hosts[0] 89 elif fail_if_not_found: 90 err_msg = _('Could not find iSCSI target %s') 91 raise exceptions.ISCSITargetException(err_msg % target_name) 92 93 def _get_wt_disk(self, description, fail_if_not_found=True): 94 # We can retrieve WT Disks only by description. 95 wt_disks = self._conn_wmi.WT_Disk(Description=description) 96 if wt_disks: 97 return wt_disks[0] 98 elif fail_if_not_found: 99 err_msg = _('Could not find WT Disk: %s') 100 raise exceptions.ISCSITargetException(err_msg % description) 101 102 def _get_wt_snapshot(self, description, fail_if_not_found=True): 103 wt_snapshots = self._conn_wmi.WT_Snapshot(Description=description) 104 if wt_snapshots: 105 return wt_snapshots[0] 106 elif fail_if_not_found: 107 err_msg = _('Could not find WT Snapshot: %s') 108 raise exceptions.ISCSITargetException(err_msg % description) 109 110 def _get_wt_idmethod(self, initiator, target_name): 111 wt_idmethod = self._conn_wmi.WT_IDMethod(HostName=target_name, 112 Value=initiator) 113 if wt_idmethod: 114 return wt_idmethod[0] 115 116 def create_iscsi_target(self, target_name, fail_if_exists=False): 117 """Creates ISCSI target.""" 118 try: 119 self._conn_wmi.WT_Host.NewHost(HostName=target_name) 120 except exceptions.x_wmi as wmi_exc: 121 err_code = _utils.get_com_error_code(wmi_exc.com_error) 122 target_exists = err_code == self._ERR_FILE_EXISTS 123 124 if not target_exists or fail_if_exists: 125 err_msg = _('Failed to create iSCSI target: %s.') 126 raise exceptions.ISCSITargetWMIException(err_msg % target_name, 127 wmi_exc=wmi_exc) 128 else: 129 LOG.info('The iSCSI target %s already exists.', 130 target_name) 131 132 def delete_iscsi_target(self, target_name): 133 """Removes ISCSI target.""" 134 try: 135 wt_host = self._get_wt_host(target_name, fail_if_not_found=False) 136 if not wt_host: 137 LOG.debug('Skipping deleting target %s as it does not ' 138 'exist.', target_name) 139 return 140 wt_host.RemoveAllWTDisks() 141 wt_host.Delete_() 142 except exceptions.x_wmi as wmi_exc: 143 err_msg = _("Failed to delete ISCSI target %s") 144 raise exceptions.ISCSITargetWMIException(err_msg % target_name, 145 wmi_exc=wmi_exc) 146 147 def iscsi_target_exists(self, target_name): 148 wt_host = self._get_wt_host(target_name, fail_if_not_found=False) 149 return wt_host is not None 150 151 def get_target_information(self, target_name): 152 wt_host = self._get_wt_host(target_name) 153 154 info = {} 155 info['target_iqn'] = wt_host.TargetIQN 156 info['enabled'] = wt_host.Enabled 157 info['connected'] = bool(wt_host.Status) 158 159 # Note(lpetrut): Cinder uses only one-way CHAP authentication. 160 if wt_host.EnableCHAP: 161 info['auth_method'] = 'CHAP' 162 info['auth_username'] = wt_host.CHAPUserName 163 info['auth_password'] = wt_host.CHAPSecret 164 165 return info 166 167 def set_chap_credentials(self, target_name, chap_username, chap_password): 168 try: 169 wt_host = self._get_wt_host(target_name) 170 wt_host.EnableCHAP = True 171 wt_host.CHAPUserName = chap_username 172 wt_host.CHAPSecret = chap_password 173 wt_host.put() 174 except exceptions.x_wmi as wmi_exc: 175 err_msg = _('Failed to set CHAP credentials on target %s.') 176 raise exceptions.ISCSITargetWMIException(err_msg % target_name, 177 wmi_exc=wmi_exc) 178 179 def associate_initiator_with_iscsi_target(self, initiator, 180 target_name, 181 id_method=ID_METHOD_IQN): 182 wt_idmethod = self._get_wt_idmethod(initiator, target_name) 183 if wt_idmethod: 184 return 185 186 try: 187 wt_idmethod = self._conn_wmi.WT_IDMethod.new() 188 wt_idmethod.HostName = target_name 189 wt_idmethod.Method = id_method 190 wt_idmethod.Value = initiator 191 wt_idmethod.put() 192 except exceptions.x_wmi as wmi_exc: 193 err_msg = _('Could not associate initiator %(initiator)s to ' 194 'iSCSI target: %(target_name)s.') 195 raise exceptions.ISCSITargetWMIException( 196 err_msg % dict(initiator=initiator, 197 target_name=target_name), 198 wmi_exc=wmi_exc) 199 200 def deassociate_initiator(self, initiator, target_name): 201 try: 202 wt_idmethod = self._get_wt_idmethod(initiator, target_name) 203 if wt_idmethod: 204 wt_idmethod.Delete_() 205 except exceptions.x_wmi as wmi_exc: 206 err_msg = _('Could not deassociate initiator %(initiator)s from ' 207 'iSCSI target: %(target_name)s.') 208 raise exceptions.ISCSITargetWMIException( 209 err_msg % dict(initiator=initiator, 210 target_name=target_name), 211 wmi_exc=wmi_exc) 212 213 def create_wt_disk(self, vhd_path, wtd_name, size_mb=None): 214 try: 215 self._conn_wmi.WT_Disk.NewWTDisk(DevicePath=vhd_path, 216 Description=wtd_name, 217 SizeInMB=size_mb) 218 except exceptions.x_wmi as wmi_exc: 219 err_msg = _('Failed to create WT Disk. ' 220 'VHD path: %(vhd_path)s ' 221 'WT disk name: %(wtd_name)s') 222 raise exceptions.ISCSITargetWMIException( 223 err_msg % dict(vhd_path=vhd_path, 224 wtd_name=wtd_name), 225 wmi_exc=wmi_exc) 226 227 def import_wt_disk(self, vhd_path, wtd_name): 228 """Import a vhd/x image to be used by Windows iSCSI targets.""" 229 try: 230 self._conn_wmi.WT_Disk.ImportWTDisk(DevicePath=vhd_path, 231 Description=wtd_name) 232 except exceptions.x_wmi as wmi_exc: 233 err_msg = _("Failed to import WT disk: %s.") 234 raise exceptions.ISCSITargetWMIException(err_msg % vhd_path, 235 wmi_exc=wmi_exc) 236 237 def change_wt_disk_status(self, wtd_name, enabled): 238 try: 239 wt_disk = self._get_wt_disk(wtd_name) 240 wt_disk.Enabled = enabled 241 wt_disk.put() 242 except exceptions.x_wmi as wmi_exc: 243 err_msg = _('Could not change disk status. WT Disk name: %s') 244 raise exceptions.ISCSITargetWMIException(err_msg % wtd_name, 245 wmi_exc=wmi_exc) 246 247 def remove_wt_disk(self, wtd_name): 248 try: 249 wt_disk = self._get_wt_disk(wtd_name, fail_if_not_found=False) 250 if wt_disk: 251 wt_disk.Delete_() 252 except exceptions.x_wmi as wmi_exc: 253 err_msg = _("Failed to remove WT disk: %s.") 254 raise exceptions.ISCSITargetWMIException(err_msg % wtd_name, 255 wmi_exc=wmi_exc) 256 257 def extend_wt_disk(self, wtd_name, additional_mb): 258 try: 259 wt_disk = self._get_wt_disk(wtd_name) 260 wt_disk.Extend(additional_mb) 261 except exceptions.x_wmi as wmi_exc: 262 err_msg = _('Could not extend WT Disk %(wtd_name)s ' 263 'with additional %(additional_mb)s MB.') 264 raise exceptions.ISCSITargetWMIException( 265 err_msg % dict(wtd_name=wtd_name, 266 additional_mb=additional_mb), 267 wmi_exc=wmi_exc) 268 269 def add_disk_to_target(self, wtd_name, target_name): 270 """Adds the disk to the target.""" 271 try: 272 wt_disk = self._get_wt_disk(wtd_name) 273 wt_host = self._get_wt_host(target_name) 274 wt_host.AddWTDisk(wt_disk.WTD) 275 except exceptions.x_wmi as wmi_exc: 276 err_msg = _('Could not add WTD Disk %(wtd_name)s to ' 277 'iSCSI target %(target_name)s.') 278 raise exceptions.ISCSITargetWMIException( 279 err_msg % dict(wtd_name=wtd_name, 280 target_name=target_name), 281 wmi_exc=wmi_exc) 282 283 def create_snapshot(self, wtd_name, snapshot_name): 284 """Driver entry point for creating a snapshot.""" 285 try: 286 wt_disk = self._get_wt_disk(wtd_name) 287 snap_id = self._conn_wmi.WT_Snapshot.Create(WTD=wt_disk.WTD)[0] 288 289 wt_snap = self._conn_wmi.WT_Snapshot(Id=snap_id)[0] 290 wt_snap.Description = snapshot_name 291 wt_snap.put() 292 except exceptions.x_wmi as wmi_exc: 293 err_msg = _('Failed to create snapshot. ' 294 'WT Disk name: %(wtd_name)s ' 295 'Snapshot name: %(snapshot_name)s') 296 raise exceptions.ISCSITargetWMIException( 297 err_msg % dict(wtd_name=wtd_name, 298 snapshot_name=snapshot_name), 299 wmi_exc=wmi_exc) 300 301 def export_snapshot(self, snapshot_name, dest_path): 302 """Driver entry point for exporting snapshots as volumes.""" 303 try: 304 wt_snap = self._get_wt_snapshot(snapshot_name) 305 wt_disk_id = wt_snap.Export()[0] 306 # This export is a read-only shadow copy, needing to be copied 307 # to another disk. 308 wt_disk = self._conn_wmi.WT_Disk(WTD=wt_disk_id)[0] 309 wt_disk.Description = '%s-%s-temp' % (snapshot_name, wt_disk_id) 310 wt_disk.put() 311 src_path = wt_disk.DevicePath 312 313 self._pathutils.copy(src_path, dest_path) 314 315 wt_disk.Delete_() 316 except exceptions.x_wmi as wmi_exc: 317 err_msg = _('Failed to export snapshot %(snapshot_name)s ' 318 'to %(dest_path)s.') 319 raise exceptions.ISCSITargetWMIException( 320 err_msg % dict(snapshot_name=snapshot_name, 321 dest_path=dest_path), 322 wmi_exc=wmi_exc) 323 324 def delete_snapshot(self, snapshot_name): 325 """Driver entry point for deleting a snapshot.""" 326 try: 327 wt_snapshot = self._get_wt_snapshot(snapshot_name, 328 fail_if_not_found=False) 329 if wt_snapshot: 330 wt_snapshot.Delete_() 331 except exceptions.x_wmi as wmi_exc: 332 err_msg = _('Failed delete snapshot %s.') 333 raise exceptions.ISCSITargetWMIException(err_msg % snapshot_name, 334 wmi_exc=wmi_exc) 335