310 lines
12 KiB
Python
310 lines
12 KiB
Python
# 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."""
|
|
from six.moves import builtins
|
|
return builtins.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)
|