Properly handle shared storage during live migration

After a live migration is performed, we always try to delete the
instance files.

If the migration fails, the destination node is not cleaned up as
the HyperVLiveMigrateData object is not used at the moment and the
nova compute manager won't know whether a cleanup is needed or not.

Also, in order to support using CSVs for storing instance files, we
need to explicitly specify whether image files will be migrated or not
when requesting os-win to migrate the VM.

This change addresses those issues.

Depends-On: I48dc6da29534bf0c477c742311ac15438d8b63ad

Change-Id: Icf2f258ef8900b5970ff153a7b981ee53e58dd2d
Partial-Bug: #1565895
This commit is contained in:
Lucian Petrut 2016-09-22 15:46:45 +03:00
parent 94558ef556
commit e2d304810f
6 changed files with 156 additions and 52 deletions

View File

@ -67,11 +67,13 @@ class ClusterLiveMigrationOps(livemigrationops.LiveMigrationOps):
with excutils.save_and_reraise_exception():
LOG.debug("Calling live migration recover_method "
"for instance.", instance=instance_ref)
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.",
instance=instance_ref)
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):
@ -81,7 +83,8 @@ class ClusterLiveMigrationOps(livemigrationops.LiveMigrationOps):
super(ClusterLiveMigrationOps, self).pre_live_migration(
context, instance, block_device_info, network_info)
def post_live_migration(self, context, instance, block_device_info):
def post_live_migration(self, context, instance, block_device_info,
migrate_data):
if not self._is_instance_clustered(instance.name):
super(ClusterLiveMigrationOps, self).post_live_migration(
context, instance, block_device_info)
context, instance, block_device_info, migrate_data)

View File

@ -240,18 +240,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):
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_source(self, context, instance, network_info):
"""Unplug VIFs from networks at source."""

View File

@ -50,28 +50,41 @@ class LiveMigrationOps(object):
LOG.debug("live_migration called", instance=instance_ref)
instance_name = instance_ref["name"]
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)
shared_storage = (
self._pathutils.check_remote_instances_dir_shared(dest))
if not shared_storage:
self._vmops.copy_vm_dvd_disks(instance_name, dest)
self._pathutils.copy_vm_console_logs(instance_name, dest)
self._livemigrutils.live_migrate_vm(instance_name,
dest)
self._livemigrutils.live_migrate_vm(
instance_name,
dest,
migrate_disks=not shared_storage)
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):
@ -98,11 +111,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):
@ -114,8 +130,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")

View File

@ -50,14 +50,16 @@ class ClusterLiveMigrationOpsTestCase(test_base.HyperVBaseTestCase):
self.livemigrops.live_migration(
self._fake_context, mock_instance, dest, post_method,
mock.sentinel.recover_method, block_migration=False,
migrate_data=None)
mock.sentinel.recover_method,
block_migration=mock.sentinel.block_migration,
migrate_data=mock.sentinel.migrate_data)
clustutils = self.livemigrops._clustutils
clustutils.live_migrate_vm.assert_called_once_with(
mock_instance.name, dest)
post_method.assert_called_once_with(
self._fake_context, mock_instance, dest, False)
self._fake_context, mock_instance, dest,
mock.sentinel.block_migration, mock.sentinel.migrate_data)
def test_live_migration_in_cluster_exception(self):
mock_instance = fake_instance.fake_instance_obj(self._fake_context)
@ -75,10 +77,14 @@ class ClusterLiveMigrationOpsTestCase(test_base.HyperVBaseTestCase):
os_win_exc.HyperVVMNotFoundException,
self.livemigrops.live_migration,
self._fake_context, mock_instance, dest, mock.sentinel.post_method,
recover_method, block_migration=False, migrate_data=None)
recover_method,
block_migration=mock.sentinel.block_migration,
migrate_data=mock.sentinel.migrate_data)
recover_method.assert_called_once_with(
self._fake_context, mock_instance, dest, False)
self._fake_context, mock_instance, dest,
mock.sentinel.block_migration,
mock.sentinel.migrate_data)
@mock.patch.object(base_livemigrationops.LiveMigrationOps,
'live_migration')
@ -126,7 +132,8 @@ class ClusterLiveMigrationOpsTestCase(test_base.HyperVBaseTestCase):
def test_post_live_migration_clustered(self, mock_post_live_migration):
self.livemigrops.post_live_migration(self._fake_context,
mock.sentinel.fake_instance,
mock.sentinel.bdi)
mock.sentinel.bdi,
mock.sentinel.migrate_data)
self.assertFalse(mock_post_live_migration.called)
@ -136,8 +143,10 @@ class ClusterLiveMigrationOpsTestCase(test_base.HyperVBaseTestCase):
self.livemigrops._clustutils.vm_exists.return_value = False
self.livemigrops.post_live_migration(self._fake_context,
mock.sentinel.fake_instance,
mock.sentinel.bdi)
mock.sentinel.bdi,
mock.sentinel.migrate_data)
mock_post_live_migration.assert_called_once_with(
self._fake_context, mock.sentinel.fake_instance,
mock.sentinel.bdi)
mock.sentinel.bdi,
mock.sentinel.migrate_data)

View File

@ -307,14 +307,16 @@ class HyperVDriverTestCase(test_base.HyperVBaseTestCase):
mock_destroy.assert_called_once_with(
mock.sentinel.context, mock.sentinel.instance,
mock.sentinel.network_info, mock.sentinel.block_device_info)
mock.sentinel.network_info, mock.sentinel.block_device_info,
destroy_disks=mock.sentinel.destroy_disks)
def test_pre_live_migration(self):
self.driver.pre_live_migration(
migrate_data = self.driver.pre_live_migration(
mock.sentinel.context, mock.sentinel.instance,
mock.sentinel.block_device_info, mock.sentinel.network_info,
mock.sentinel.disk_info, mock.sentinel.migrate_data)
self.assertEqual(mock.sentinel.migrate_data, migrate_data)
pre_live_migration = self.driver._livemigrationops.pre_live_migration
pre_live_migration.assert_called_once_with(
mock.sentinel.context, mock.sentinel.instance,
@ -328,7 +330,8 @@ class HyperVDriverTestCase(test_base.HyperVBaseTestCase):
post_live_migration = self.driver._livemigrationops.post_live_migration
post_live_migration.assert_called_once_with(
mock.sentinel.context, mock.sentinel.instance,
mock.sentinel.block_device_info)
mock.sentinel.block_device_info,
mock.sentinel.migrate_data)
def test_post_live_migration_at_source(self):
self.driver.post_live_migration_at_source(

View File

@ -13,7 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
import ddt
import mock
from nova.objects import migrate_data as migrate_data_obj
from os_win import exceptions as os_win_exc
from oslo_config import cfg
@ -25,6 +27,7 @@ from hyperv.tests.unit import test_base
CONF = cfg.CONF
@ddt.ddt
class LiveMigrationOpsTestCase(test_base.HyperVBaseTestCase):
"""Unit tests for the Hyper-V LiveMigrationOps class."""
@ -34,13 +37,21 @@ class LiveMigrationOpsTestCase(test_base.HyperVBaseTestCase):
self._livemigrops = livemigrationops.LiveMigrationOps()
self._livemigrops._block_dev_man = mock.MagicMock()
self._livemigrops._pathutils = mock.MagicMock()
self._pathutils = self._livemigrops._pathutils
@ddt.data({'migrate_data_received': False},
{'migrate_data_version': '1.0'},
{'shared_storage': True},
{'side_effect': os_win_exc.HyperVException})
@ddt.unpack
@mock.patch.object(serialconsoleops.SerialConsoleOps,
'stop_console_handler')
@mock.patch('hyperv.nova.vmops.VMOps.copy_vm_dvd_disks')
def _test_live_migration(self, mock_copy_dvd_disks,
mock_stop_console_handler, side_effect=None,
shared_storage=False):
def test_live_migration(self, mock_copy_dvd_disks,
mock_stop_console_handler, side_effect=None,
shared_storage=False,
migrate_data_received=True,
migrate_data_version='1.1'):
mock_instance = fake_instance.fake_instance_obj(self.context)
mock_post = mock.MagicMock()
mock_recover = mock.MagicMock()
@ -52,23 +63,56 @@ class LiveMigrationOpsTestCase(test_base.HyperVBaseTestCase):
self._livemigrops._pathutils.check_remote_instances_dir_shared)
mock_check_shared_storage.return_value = shared_storage
if migrate_data_received:
migrate_data = migrate_data_obj.HyperVLiveMigrateData()
if migrate_data_version != '1.0':
migrate_data.is_shared_instance_path = shared_storage
else:
migrate_data = None
if side_effect is os_win_exc.HyperVException:
self.assertRaises(os_win_exc.HyperVException,
self._livemigrops.live_migration,
self.context, mock_instance, fake_dest,
mock_post, mock_recover, False, None)
mock_post, mock_recover,
mock.sentinel.block_migr,
migrate_data)
mock_recover.assert_called_once_with(self.context, mock_instance,
fake_dest, False)
fake_dest,
mock.sentinel.block_migr,
migrate_data)
else:
self._livemigrops.live_migration(context=self.context,
instance_ref=mock_instance,
dest=fake_dest,
post_method=mock_post,
recover_method=mock_recover)
recover_method=mock_recover,
block_migration=(
mock.sentinel.block_migr),
migrate_data=migrate_data)
mock_post.assert_called_once_with(self.context,
mock_instance,
fake_dest,
mock.sentinel.block_migr,
mock.ANY)
# The last argument, the migrate_data object, should be created
# by the callee if not received.
post_call_args_list = mock_post.call_args_list[0][0]
migrate_data_arg = post_call_args_list[-1]
self.assertIsInstance(
migrate_data_arg,
migrate_data_obj.HyperVLiveMigrateData)
self.assertEqual(shared_storage,
migrate_data_arg.is_shared_instance_path)
if not migrate_data_received or migrate_data_version == '1.0':
mock_check_shared_storage.assert_called_once_with(fake_dest)
else:
self.assertFalse(mock_check_shared_storage.called)
mock_stop_console_handler.assert_called_once_with(
mock_instance.name)
mock_check_shared_storage.assert_called_once_with(fake_dest)
if not shared_storage:
mock_copy_logs.assert_called_once_with(mock_instance.name,
@ -81,18 +125,9 @@ class LiveMigrationOpsTestCase(test_base.HyperVBaseTestCase):
mock_live_migr = self._livemigrops._livemigrutils.live_migrate_vm
mock_live_migr.assert_called_once_with(mock_instance.name,
fake_dest)
mock_post.assert_called_once_with(self.context, mock_instance,
fake_dest, False)
def test_live_migration(self):
self._test_live_migration()
def test_live_migration_shared_storage(self):
self._test_live_migration(shared_storage=True)
def test_live_migration_exception(self):
self._test_live_migration(side_effect=os_win_exc.HyperVException)
fake_dest,
migrate_disks=(
not shared_storage))
@mock.patch('hyperv.nova.volumeops.VolumeOps.get_disk_path_mapping')
@mock.patch('hyperv.nova.imagecache.ImageCache.get_cached_image')
@ -140,15 +175,29 @@ class LiveMigrationOpsTestCase(test_base.HyperVBaseTestCase):
def test_pre_live_migration_without_phys_disks_attached(self):
self._test_pre_live_migration(phys_disks_attached=False)
@ddt.data({},
{'shared_storage': True})
@ddt.unpack
@mock.patch('hyperv.nova.volumeops.VolumeOps.disconnect_volumes')
def test_post_live_migration(self, mock_disconnect_volumes):
def test_post_live_migration(self, mock_disconnect_volumes,
shared_storage=False):
migrate_data = migrate_data_obj.HyperVLiveMigrateData(
is_shared_instance_path=shared_storage)
self._livemigrops.post_live_migration(
self.context, mock.sentinel.instance,
mock.sentinel.block_device_info)
mock.sentinel.block_device_info,
migrate_data)
mock_disconnect_volumes.assert_called_once_with(
mock.sentinel.block_device_info)
self._livemigrops._pathutils.get_instance_dir.assert_called_once_with(
mock.sentinel.instance.name, create_dir=False, remove_dir=True)
mock_get_inst_dir = self._pathutils.get_instance_dir
if not shared_storage:
mock_get_inst_dir.assert_called_once_with(
mock.sentinel.instance.name,
create_dir=False, remove_dir=True)
else:
self.assertFalse(mock_get_inst_dir.called)
@mock.patch.object(livemigrationops.vmops.VMOps, 'post_start_vifs')
def test_post_live_migration_at_destination(self, mock_post_start_vifs):
@ -158,3 +207,18 @@ class LiveMigrationOpsTestCase(test_base.HyperVBaseTestCase):
mock_post_start_vifs.assert_called_once_with(
mock.sentinel.instance, mock.sentinel.network_info)
@mock.patch.object(migrate_data_obj, 'HyperVLiveMigrateData')
def test_check_can_live_migrate_destination(self, mock_migr_data_cls):
mock_instance = fake_instance.fake_instance_obj(self.context)
migr_data = self._livemigrops.check_can_live_migrate_destination(
mock.sentinel.context, mock_instance, mock.sentinel.src_comp_info,
mock.sentinel.dest_comp_info)
mock_check_shared_inst_dir = (
self._pathutils.check_remote_instances_dir_shared)
mock_check_shared_inst_dir.assert_called_once_with(mock_instance.host)
self.assertEqual(mock_migr_data_cls.return_value, migr_data)
self.assertEqual(mock_check_shared_inst_dir.return_value,
migr_data.is_shared_instance_path)