diff --git a/nova/compute/manager.py b/nova/compute/manager.py index ee3adce30e7f..60c1fbf76368 100755 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -3901,6 +3901,11 @@ class ComputeManager(manager.SchedulerDependentManager): LOG.info(_('_post_live_migration() is started..'), instance=instance_ref) + # Cleanup source host post live-migration + block_device_info = self._get_instance_volume_block_device_info( + ctxt, instance_ref) + self.driver.post_live_migration(ctxt, instance_ref, block_device_info) + # Detaching volumes. connector = self.driver.get_volume_connector(instance_ref) for bdm in self._get_instance_volume_bdms(ctxt, instance_ref): diff --git a/nova/tests/compute/test_compute.py b/nova/tests/compute/test_compute.py index e1041191c8d6..a6c21b5287c6 100644 --- a/nova/tests/compute/test_compute.py +++ b/nova/tests/compute/test_compute.py @@ -19,6 +19,7 @@ """Tests for compute service.""" import base64 +import contextlib import copy import datetime import operator @@ -28,6 +29,7 @@ import time import traceback import uuid +import mock import mox from oslo.config import cfg @@ -4243,32 +4245,35 @@ class ComputeTestCase(BaseTestCase): 'power_state': power_state.PAUSED}) # creating mocks - self.mox.StubOutWithMock(self.compute.driver, 'unfilter_instance') - self.compute.driver.unfilter_instance(inst_ref, []) - self.mox.StubOutWithMock(self.compute.conductor_api, - 'network_migrate_instance_start') - migration = {'source_compute': srchost, - 'dest_compute': dest, } - self.compute.conductor_api.network_migrate_instance_start(c, inst_ref, - migration) + with contextlib.nested( + mock.patch.object(self.compute.driver, 'post_live_migration'), + mock.patch.object(self.compute.driver, 'unfilter_instance'), + mock.patch.object(self.compute.conductor_api, + 'network_migrate_instance_start'), + mock.patch.object(self.compute.compute_rpcapi, + 'post_live_migration_at_destination'), + mock.patch.object(self.compute.driver, 'unplug_vifs'), + mock.patch.object(self.compute.network_api, + 'setup_networks_on_host') + ) as ( + post_live_migration, unfilter_instance, + network_migrate_instance_start, post_live_migration_at_destination, + unplug_vifs, setup_networks_on_host + ): + self.compute._post_live_migration(c, inst_ref, dest) - self.mox.StubOutWithMock(self.compute.compute_rpcapi, - 'post_live_migration_at_destination') - self.compute.compute_rpcapi.post_live_migration_at_destination( - c, inst_ref, False, dest) - - self.mox.StubOutWithMock(self.compute.driver, 'unplug_vifs') - self.compute.driver.unplug_vifs(inst_ref, []) - - self.mox.StubOutWithMock(self.compute.network_api, - 'setup_networks_on_host') - self.compute.network_api.setup_networks_on_host(c, inst_ref, - self.compute.host, - teardown=True) - - # start test - self.mox.ReplayAll() - self.compute._post_live_migration(c, inst_ref, dest) + post_live_migration.assert_has_calls([ + mock.call(c, inst_ref, {'block_device_mapping': []})]) + unfilter_instance.assert_has_calls([mock.call(inst_ref, [])]) + migration = {'source_compute': srchost, + 'dest_compute': dest, } + network_migrate_instance_start.assert_has_calls([ + mock.call(c, inst_ref, migration)]) + post_live_migration_at_destination.assert_has_calls([ + mock.call(c, inst_ref, False, dest)]) + unplug_vifs.assert_has_calls([mock.call(inst_ref, [])]) + setup_networks_on_host.assert_has_calls([ + mock.call(c, inst_ref, self.compute.host, teardown=True)]) def _begin_post_live_migration_at_destination(self): self.mox.StubOutWithMock(self.compute.network_api, diff --git a/nova/tests/virt/libvirt/test_libvirt.py b/nova/tests/virt/libvirt/test_libvirt.py index 2f6cb4216ab0..0d9baa8e0e53 100644 --- a/nova/tests/virt/libvirt/test_libvirt.py +++ b/nova/tests/virt/libvirt/test_libvirt.py @@ -15,6 +15,7 @@ # License for the specific language governing permissions and limitations # under the License. +import contextlib import copy import errno import eventlet @@ -27,6 +28,7 @@ import tempfile from eventlet import greenthread from lxml import etree +import mock from oslo.config import cfg from xml.dom import minidom @@ -2868,6 +2870,31 @@ class LibvirtConnTestCase(test.TestCase): db.instance_destroy(self.context, instance_ref['uuid']) + def test_post_live_migration(self): + vol = {'block_device_mapping': [ + {'connection_info': 'dummy1', 'mount_device': '/dev/sda'}, + {'connection_info': 'dummy2', 'mount_device': '/dev/sdb'}]} + conn = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False) + + inst_ref = {'id': 'foo'} + cntx = context.get_admin_context() + + # Set up the mock expectations + with contextlib.nested( + mock.patch.object(driver, 'block_device_info_get_mapping', + return_value=vol['block_device_mapping']), + mock.patch.object(conn, 'volume_driver_method') + ) as (block_device_info_get_mapping, volume_driver_method): + conn.post_live_migration(cntx, inst_ref, vol) + + block_device_info_get_mapping.assert_has_calls([ + mock.call(vol)]) + volume_driver_method.assert_has_calls([ + mock.call('disconnect_volume', + v['connection_info'], + v['mount_device'].rpartition("/")[2]) + for v in vol['block_device_mapping']]) + def test_get_instance_disk_info_excludes_volumes(self): # Test data instance_ref = db.instance_create(self.context, self.test_instance) diff --git a/nova/virt/driver.py b/nova/virt/driver.py index ddcb85ee05a8..c69224c4c102 100755 --- a/nova/virt/driver.py +++ b/nova/virt/driver.py @@ -514,6 +514,15 @@ class ComputeDriver(object): """ raise NotImplementedError() + def post_live_migration(self, ctxt, instance_ref, block_device_info): + """Post operation of live migration at source host. + + :param ctxt: security contet + :instance_ref: instance object that was migrated + :block_device_info: instance block device information + """ + pass + def post_live_migration_at_destination(self, ctxt, instance_ref, network_info, block_migration=False, diff --git a/nova/virt/libvirt/driver.py b/nova/virt/libvirt/driver.py index 26ed22e90307..46944cf7f994 100755 --- a/nova/virt/libvirt/driver.py +++ b/nova/virt/libvirt/driver.py @@ -3687,6 +3687,17 @@ class LibvirtDriver(driver.ComputeDriver): # following normal way. self._fetch_instance_kernel_ramdisk(context, instance) + def post_live_migration(self, context, instance, block_device_info): + # Disconnect from volume server + block_device_mapping = driver.block_device_info_get_mapping( + block_device_info) + for vol in block_device_mapping: + connection_info = vol['connection_info'] + disk_dev = vol['mount_device'].rpartition("/")[2] + self.volume_driver_method('disconnect_volume', + connection_info, + disk_dev) + def post_live_migration_at_destination(self, context, instance, network_info,