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