Hyper-V: properly handle shared storage during migrations
In case of migrations, we attempt to move the instance files without checking whether shared storage is used. Note that the block_migration flag is ignored. After a live migration is performed, we always try to delete the instance files. Also, if the migration fails, the destination node is not cleaned up, as the HyperVLiveMigrateData object is not used at the moment. This change addresses those issues. Partial-Bug: #1565895 Change-Id: I0ac0a2d2e7a8771024a486dd5931bd05b1ecd074
This commit is contained in:
@@ -233,18 +233,21 @@ class HyperVDriver(driver.ComputeDriver):
|
||||
block_device_info,
|
||||
destroy_disks=True,
|
||||
migrate_data=None):
|
||||
self.destroy(context, instance, network_info, block_device_info)
|
||||
self.destroy(context, instance, network_info, block_device_info,
|
||||
destroy_disks=destroy_disks)
|
||||
|
||||
def pre_live_migration(self, context, instance, block_device_info,
|
||||
network_info, disk_info, migrate_data=None):
|
||||
self._livemigrationops.pre_live_migration(context, instance,
|
||||
block_device_info,
|
||||
network_info)
|
||||
return migrate_data
|
||||
|
||||
def post_live_migration(self, context, instance, block_device_info,
|
||||
migrate_data=None):
|
||||
self._livemigrationops.post_live_migration(context, instance,
|
||||
block_device_info)
|
||||
block_device_info,
|
||||
migrate_data)
|
||||
|
||||
def post_live_migration_at_destination(self, context, instance,
|
||||
network_info,
|
||||
|
||||
@@ -51,25 +51,39 @@ class LiveMigrationOps(object):
|
||||
LOG.debug("live_migration called", instance=instance_ref)
|
||||
instance_name = instance_ref["name"]
|
||||
|
||||
try:
|
||||
self._vmops.copy_vm_dvd_disks(instance_name, dest)
|
||||
if migrate_data and 'is_shared_instance_path' in migrate_data:
|
||||
shared_storage = migrate_data.is_shared_instance_path
|
||||
else:
|
||||
shared_storage = (
|
||||
self._pathutils.check_remote_instances_dir_shared(dest))
|
||||
if migrate_data:
|
||||
migrate_data.is_shared_instance_path = shared_storage
|
||||
else:
|
||||
migrate_data = migrate_data_obj.HyperVLiveMigrateData(
|
||||
is_shared_instance_path=shared_storage)
|
||||
|
||||
try:
|
||||
# We must make sure that the console log workers are stopped,
|
||||
# otherwise we won't be able to delete / move VM log files.
|
||||
self._serial_console_ops.stop_console_handler(instance_name)
|
||||
|
||||
self._pathutils.copy_vm_console_logs(instance_name, dest)
|
||||
if not shared_storage:
|
||||
self._pathutils.copy_vm_console_logs(instance_name, dest)
|
||||
self._vmops.copy_vm_dvd_disks(instance_name, dest)
|
||||
|
||||
self._livemigrutils.live_migrate_vm(instance_name,
|
||||
dest)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
LOG.debug("Calling live migration recover_method "
|
||||
"for instance: %s", instance_name)
|
||||
recover_method(context, instance_ref, dest, block_migration)
|
||||
recover_method(context, instance_ref, dest, block_migration,
|
||||
migrate_data)
|
||||
|
||||
LOG.debug("Calling live migration post_method for instance: %s",
|
||||
instance_name)
|
||||
post_method(context, instance_ref, dest, block_migration)
|
||||
post_method(context, instance_ref, dest,
|
||||
block_migration, migrate_data)
|
||||
|
||||
def pre_live_migration(self, context, instance, block_device_info,
|
||||
network_info):
|
||||
@@ -93,11 +107,14 @@ class LiveMigrationOps(object):
|
||||
instance.host,
|
||||
disk_path_mapping)
|
||||
|
||||
def post_live_migration(self, context, instance, block_device_info):
|
||||
def post_live_migration(self, context, instance, block_device_info,
|
||||
migrate_data):
|
||||
self._volumeops.disconnect_volumes(block_device_info)
|
||||
self._pathutils.get_instance_dir(instance.name,
|
||||
create_dir=False,
|
||||
remove_dir=True)
|
||||
|
||||
if not migrate_data.is_shared_instance_path:
|
||||
self._pathutils.get_instance_dir(instance.name,
|
||||
create_dir=False,
|
||||
remove_dir=True)
|
||||
|
||||
def post_live_migration_at_destination(self, ctxt, instance_ref,
|
||||
network_info, block_migration):
|
||||
@@ -108,8 +125,14 @@ class LiveMigrationOps(object):
|
||||
src_compute_info, dst_compute_info,
|
||||
block_migration=False,
|
||||
disk_over_commit=False):
|
||||
LOG.debug("check_can_live_migrate_destination called", instance_ref)
|
||||
return migrate_data_obj.HyperVLiveMigrateData()
|
||||
LOG.debug("check_can_live_migrate_destination called",
|
||||
instance=instance_ref)
|
||||
|
||||
migrate_data = migrate_data_obj.HyperVLiveMigrateData()
|
||||
migrate_data.is_shared_instance_path = (
|
||||
self._pathutils.check_remote_instances_dir_shared(
|
||||
instance_ref.host))
|
||||
return migrate_data
|
||||
|
||||
def cleanup_live_migration_destination_check(self, ctxt, dest_check_data):
|
||||
LOG.debug("cleanup_live_migration_destination_check called")
|
||||
|
||||
@@ -39,7 +39,6 @@ LOG = logging.getLogger(__name__)
|
||||
|
||||
class MigrationOps(object):
|
||||
def __init__(self):
|
||||
self._hostutils = utilsfactory.get_hostutils()
|
||||
self._vmutils = utilsfactory.get_vmutils()
|
||||
self._vhdutils = utilsfactory.get_vhdutils()
|
||||
self._pathutils = pathutils.PathUtils()
|
||||
@@ -51,29 +50,27 @@ class MigrationOps(object):
|
||||
def _migrate_disk_files(self, instance_name, disk_files, dest):
|
||||
# TODO(mikal): it would be nice if this method took a full instance,
|
||||
# because it could then be passed to the log messages below.
|
||||
same_host = False
|
||||
if dest in self._hostutils.get_local_ips():
|
||||
same_host = True
|
||||
LOG.debug("Migration target is the source host")
|
||||
else:
|
||||
LOG.debug("Migration target host: %s", dest)
|
||||
|
||||
instance_path = self._pathutils.get_instance_dir(instance_name)
|
||||
dest_path = self._pathutils.get_instance_dir(instance_name, dest)
|
||||
revert_path = self._pathutils.get_instance_migr_revert_dir(
|
||||
instance_name, remove_dir=True, create_dir=True)
|
||||
dest_path = None
|
||||
|
||||
shared_storage = (self._pathutils.exists(dest_path) and
|
||||
self._pathutils.check_dirs_shared_storage(
|
||||
instance_path, dest_path))
|
||||
|
||||
try:
|
||||
if same_host:
|
||||
if shared_storage:
|
||||
# Since source and target are the same, we copy the files to
|
||||
# a temporary location before moving them into place
|
||||
# a temporary location before moving them into place.
|
||||
# This applies when the migration target is the source host or
|
||||
# when shared storage is used for the instance files.
|
||||
dest_path = '%s_tmp' % instance_path
|
||||
if self._pathutils.exists(dest_path):
|
||||
self._pathutils.rmtree(dest_path)
|
||||
self._pathutils.makedirs(dest_path)
|
||||
else:
|
||||
dest_path = self._pathutils.get_instance_dir(
|
||||
instance_name, dest, remove_dir=True)
|
||||
|
||||
self._pathutils.check_remove_dir(dest_path)
|
||||
self._pathutils.makedirs(dest_path)
|
||||
|
||||
for disk_file in disk_files:
|
||||
# Skip the config drive as the instance is already configured
|
||||
if os.path.basename(disk_file).lower() != 'configdrive.vhd':
|
||||
@@ -84,8 +81,9 @@ class MigrationOps(object):
|
||||
|
||||
self._pathutils.move_folder_files(instance_path, revert_path)
|
||||
|
||||
if same_host:
|
||||
if shared_storage:
|
||||
self._pathutils.move_folder_files(dest_path, instance_path)
|
||||
self._pathutils.rmtree(dest_path)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
self._cleanup_failed_disk_migration(instance_path, revert_path,
|
||||
|
||||
@@ -14,15 +14,19 @@
|
||||
# under the License.
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
from os_win.utils import pathutils
|
||||
from oslo_log import log as logging
|
||||
|
||||
import nova.conf
|
||||
from nova import exception
|
||||
from nova.i18n import _
|
||||
from nova.virt.hyperv import constants
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
CONF = nova.conf.CONF
|
||||
|
||||
ERROR_INVALID_NAME = 123
|
||||
@@ -169,3 +173,25 @@ class PathUtils(pathutils.PathUtils):
|
||||
|
||||
def get_age_of_file(self, file_name):
|
||||
return time.time() - os.path.getmtime(file_name)
|
||||
|
||||
def check_dirs_shared_storage(self, src_dir, dest_dir):
|
||||
# Check if shared storage is being used by creating a temporary
|
||||
# file at the destination path and checking if it exists at the
|
||||
# source path.
|
||||
LOG.debug("Checking if %(src_dir)s and %(dest_dir)s point "
|
||||
"to the same location.",
|
||||
dict(src_dir=src_dir, dest_dir=dest_dir))
|
||||
with tempfile.NamedTemporaryFile(dir=dest_dir) as tmp_file:
|
||||
src_path = os.path.join(src_dir,
|
||||
os.path.basename(tmp_file.name))
|
||||
|
||||
shared_storage = os.path.exists(src_path)
|
||||
return shared_storage
|
||||
|
||||
def check_remote_instances_dir_shared(self, dest):
|
||||
# Checks if the instances dir from a remote host points
|
||||
# to the same storage location as the local instances dir.
|
||||
local_inst_dir = self.get_instances_dir()
|
||||
remote_inst_dir = self.get_instances_dir(dest)
|
||||
return self.check_dirs_shared_storage(local_inst_dir,
|
||||
remote_inst_dir)
|
||||
|
||||
Reference in New Issue
Block a user