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 16import os 17import socket 18 19from oslo_log import log as logging 20 21from os_win._i18n import _ 22from os_win import _utils 23from os_win import exceptions 24from os_win.utils import baseutils 25from os_win.utils import win32utils 26 27LOG = logging.getLogger(__name__) 28 29 30class SMBUtils(baseutils.BaseUtils): 31 _loopback_share_map = {} 32 33 def __init__(self): 34 self._win32_utils = win32utils.Win32Utils() 35 self._smb_conn = self._get_wmi_conn(r"root\Microsoft\Windows\SMB") 36 37 def check_smb_mapping(self, share_path, remove_unavailable_mapping=False): 38 mappings = self._smb_conn.Msft_SmbMapping(RemotePath=share_path) 39 40 if not mappings: 41 return False 42 43 if os.path.exists(share_path): 44 LOG.debug('Share already mounted: %s', share_path) 45 return True 46 else: 47 LOG.debug('Share exists but is unavailable: %s ', share_path) 48 if remove_unavailable_mapping: 49 self.unmount_smb_share(share_path, force=True) 50 return False 51 52 def mount_smb_share(self, share_path, username=None, password=None): 53 try: 54 LOG.debug('Mounting share: %s', share_path) 55 self._smb_conn.Msft_SmbMapping.Create(RemotePath=share_path, 56 UserName=username, 57 Password=password) 58 except exceptions.x_wmi as exc: 59 err_msg = (_( 60 'Unable to mount SMBFS share: %(share_path)s ' 61 'WMI exception: %(wmi_exc)s') % {'share_path': share_path, 62 'wmi_exc': exc}) 63 raise exceptions.SMBException(err_msg) 64 65 def unmount_smb_share(self, share_path, force=False): 66 mappings = self._smb_conn.Msft_SmbMapping(RemotePath=share_path) 67 if not mappings: 68 LOG.debug('Share %s is not mounted. Skipping unmount.', 69 share_path) 70 71 for mapping in mappings: 72 # Due to a bug in the WMI module, getting the output of 73 # methods returning None will raise an AttributeError 74 try: 75 mapping.Remove(Force=force) 76 except AttributeError: 77 pass 78 except exceptions.x_wmi: 79 # If this fails, a 'Generic Failure' exception is raised. 80 # This happens even if we unforcefully unmount an in-use 81 # share, for which reason we'll simply ignore it in this 82 # case. 83 if force: 84 raise exceptions.SMBException( 85 _("Could not unmount share: %s") % share_path) 86 87 def get_smb_share_path(self, share_name): 88 shares = self._smb_conn.Msft_SmbShare(Name=share_name) 89 share_path = shares[0].Path if shares else None 90 if not shares: 91 LOG.debug("Could not find any local share named %s.", share_name) 92 return share_path 93 94 def is_local_share(self, share_path): 95 # In case of Scale-Out File Servers, we'll get the Distributed Node 96 # Name of the share. We have to check whether this resolves to a 97 # local ip, which would happen in a hyper converged scenario. 98 # 99 # In this case, mounting the share is not supported and we have to 100 # use the local share path. 101 if share_path in self._loopback_share_map: 102 return self._loopback_share_map[share_path] 103 104 addr = share_path.lstrip('\\').split('\\', 1)[0] 105 106 local_ips = _utils.get_ips(socket.gethostname()) 107 local_ips += _utils.get_ips('localhost') 108 109 dest_ips = _utils.get_ips(addr) 110 is_local = bool(set(local_ips).intersection(set(dest_ips))) 111 112 self._loopback_share_map[share_path] = is_local 113 return is_local 114