# Copyright 2013 Cloudbase Solutions Srl # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import os import shutil import sys import time if sys.platform == 'win32': import wmi from nova import utils from oslo_config import cfg from oslo_log import log as logging from hyperv.i18n import _ from hyperv.nova import constants from hyperv.nova import vmutils LOG = logging.getLogger(__name__) hyperv_opts = [ cfg.StrOpt('instances_path_share', default="", help='The name of a Windows share name mapped to the ' '"instances_path" dir and used by the resize feature ' 'to copy files to the target host. If left blank, an ' 'administrative share will be used, looking for the same ' '"instances_path" used locally'), ] CONF = cfg.CONF CONF.register_opts(hyperv_opts, 'hyperv') CONF.import_opt('instances_path', 'nova.compute.manager') ERROR_INVALID_NAME = 123 ERROR_DIR_IS_NOT_EMPTY = 145 class PathUtils(object): def __init__(self): self._set_smb_conn() @property def _smb_conn(self): if self._smb_conn_attr: return self._smb_conn_attr raise vmutils.HyperVException(_("The SMB WMI namespace is not " "available on this OS version.")) def _set_smb_conn(self): # The following namespace is not available prior to Windows # Server 2012. utilsfactory is not used in order to avoid a # circular dependency. try: self._smb_conn_attr = wmi.WMI( moniker=r"root\Microsoft\Windows\SMB") except wmi.x_wmi: self._smb_conn_attr = None def open(self, path, mode): """Wrapper on __builtin__.open used to simplify unit testing.""" import __builtin__ return __builtin__.open(path, mode) def exists(self, path): return os.path.exists(path) def makedirs(self, path): os.makedirs(path) def remove(self, path): os.remove(path) def rename(self, src, dest): os.rename(src, dest) def copyfile(self, src, dest): self.copy(src, dest) def copy(self, src, dest): # With large files this is 2x-3x faster than shutil.copy(src, dest), # especially when copying to a UNC target. # shutil.copyfileobj(...) with a proper buffer is better than # shutil.copy(...) but still 20% slower than a shell copy. # It can be replaced with Win32 API calls to avoid the process # spawning overhead. LOG.debug('Copying file from %s to %s', src, dest) output, ret = utils.execute('cmd.exe', '/C', 'copy', '/Y', src, dest) if ret: raise IOError(_('The file copy from %(src)s to %(dest)s failed') % {'src': src, 'dest': dest}) def move_folder_files(self, src_dir, dest_dir): """Moves the files of the given src_dir to dest_dir. It will ignore any nested folders. :param src_dir: Given folder from which to move files. :param dest_dir: Folder to which to move files. """ for fname in os.listdir(src_dir): src = os.path.join(src_dir, fname) # ignore subdirs. if os.path.isfile(src): self.rename(src, os.path.join(dest_dir, fname)) def rmtree(self, path): # This will be removed once support for Windows Server 2008R2 is # stopped for i in range(5): try: shutil.rmtree(path) return except WindowsError as e: if e.winerror == ERROR_DIR_IS_NOT_EMPTY: time.sleep(1) else: raise e def get_instances_dir(self, remote_server=None): local_instance_path = os.path.normpath(CONF.instances_path) if remote_server: if CONF.hyperv.instances_path_share: path = CONF.hyperv.instances_path_share else: # Use an administrative share path = local_instance_path.replace(':', '$') return ('\\\\%(remote_server)s\\%(path)s' % {'remote_server': remote_server, 'path': path}) else: return local_instance_path def _check_create_dir(self, path): if not self.exists(path): LOG.debug('Creating directory: %s', path) self.makedirs(path) def _check_remove_dir(self, path): if self.exists(path): LOG.debug('Removing directory: %s', path) self.rmtree(path) def _get_instances_sub_dir(self, dir_name, remote_server=None, create_dir=True, remove_dir=False): instances_path = self.get_instances_dir(remote_server) path = os.path.join(instances_path, dir_name) try: if remove_dir: self._check_remove_dir(path) if create_dir: self._check_create_dir(path) return path except WindowsError as ex: if ex.winerror == ERROR_INVALID_NAME: raise vmutils.HyperVException(_( "Cannot access \"%(instances_path)s\", make sure the " "path exists and that you have the proper permissions. " "In particular Nova-Compute must not be executed with the " "builtin SYSTEM account or other accounts unable to " "authenticate on a remote host.") % {'instances_path': instances_path}) raise def get_instance_migr_revert_dir(self, instance_name, create_dir=False, remove_dir=False): dir_name = '%s_revert' % instance_name return self._get_instances_sub_dir(dir_name, None, create_dir, remove_dir) def get_instance_dir(self, instance_name, remote_server=None, create_dir=True, remove_dir=False): return self._get_instances_sub_dir(instance_name, remote_server, create_dir, remove_dir) def _lookup_vhd_path(self, instance_name, vhd_path_func, *args, **kwargs): vhd_path = None for format_ext in ['vhd', 'vhdx']: test_path = vhd_path_func(instance_name, format_ext, *args, **kwargs) if self.exists(test_path): vhd_path = test_path break return vhd_path def lookup_root_vhd_path(self, instance_name, rescue=False): return self._lookup_vhd_path(instance_name, self.get_root_vhd_path, rescue) def lookup_configdrive_path(self, instance_name, rescue=False): configdrive_path = None for format_ext in constants.DISK_FORMAT_MAP: test_path = self.get_configdrive_path(instance_name, format_ext, rescue=rescue) if self.exists(test_path): configdrive_path = test_path break return configdrive_path def lookup_ephemeral_vhd_path(self, instance_name): return self._lookup_vhd_path(instance_name, self.get_ephemeral_vhd_path) def get_root_vhd_path(self, instance_name, format_ext, rescue=False): instance_path = self.get_instance_dir(instance_name) image_name = 'rescue' if rescue else 'root' return os.path.join(instance_path, image_name + '.' + format_ext.lower()) def get_configdrive_path(self, instance_name, format_ext, remote_server=None, rescue=False): instance_path = self.get_instance_dir(instance_name, remote_server) configdrive_image_name = 'configdrive' if rescue: configdrive_image_name += '-rescue' return os.path.join(instance_path, configdrive_image_name + '.' + format_ext.lower()) def get_ephemeral_vhd_path(self, instance_name, format_ext): instance_path = self.get_instance_dir(instance_name) return os.path.join(instance_path, 'ephemeral.' + format_ext.lower()) def get_base_vhd_dir(self): return self._get_instances_sub_dir('_base') def get_export_dir(self, instance_name): dir_name = os.path.join('export', instance_name) return self._get_instances_sub_dir(dir_name, create_dir=True, remove_dir=True) def get_vm_console_log_paths(self, vm_name, remote_server=None): instance_dir = self.get_instance_dir(vm_name, remote_server) console_log_path = os.path.join(instance_dir, 'console.log') return console_log_path, console_log_path + '.1' def copy_vm_console_logs(self, instance_name, dest_host): local_log_paths = self.get_vm_console_log_paths( instance_name) remote_log_paths = self.get_vm_console_log_paths( instance_name, remote_server=dest_host) for local_log_path, remote_log_path in zip(local_log_paths, remote_log_paths): if self.exists(local_log_path): self.copy(local_log_path, remote_log_path) def check_smb_mapping(self, smbfs_share): mappings = self._smb_conn.Msft_SmbMapping(RemotePath=smbfs_share) if not mappings: return False if os.path.exists(smbfs_share): LOG.debug('Share already mounted: %s', smbfs_share) return True else: LOG.debug('Share exists but is unavailable: %s ', smbfs_share) self.unmount_smb_share(smbfs_share, force=True) return False def mount_smb_share(self, smbfs_share, username=None, password=None): try: LOG.debug('Mounting share: %s', smbfs_share) self._smb_conn.Msft_SmbMapping.Create(RemotePath=smbfs_share, UserName=username, Password=password) except wmi.x_wmi as exc: err_msg = (_( 'Unable to mount SMBFS share: %(smbfs_share)s ' 'WMI exception: %(wmi_exc)s') % {'smbfs_share': smbfs_share, 'wmi_exc': exc}) raise vmutils.HyperVException(err_msg) def unmount_smb_share(self, smbfs_share, force=False): mappings = self._smb_conn.Msft_SmbMapping(RemotePath=smbfs_share) if not mappings: LOG.debug('Share %s is not mounted. Skipping unmount.', smbfs_share) for mapping in mappings: # Due to a bug in the WMI module, getting the output of # methods returning None will raise an AttributeError try: mapping.Remove(Force=force) except AttributeError: pass except wmi.x_wmi: # If this fails, a 'Generic Failure' exception is raised. # This happens even if we unforcefully unmount an in-use # share, for which reason we'll simply ignore it in this # case. if force: raise vmutils.HyperVException( _("Could not unmount share: %s") % smbfs_share)