From 38e6e93a8102751b781d79da37249fe9c55f2575 Mon Sep 17 00:00:00 2001 From: Jason Dillaman Date: Wed, 26 Jun 2013 16:53:03 -0400 Subject: [PATCH] Disconnect from iSCSI volume sessions after live migration The live migration source host will now disconnect from iSCSI volume sessions after the VM is successfully migrated to the destination host. Fixes bug 1132146 Change-Id: I132869612bdf2baa810756586e643ea68ea8d8f6 --- nova/compute/manager.py | 5 +++ nova/tests/compute/test_compute.py | 55 ++++++++++++++----------- nova/tests/virt/libvirt/test_libvirt.py | 27 ++++++++++++ nova/virt/driver.py | 9 ++++ nova/virt/libvirt/driver.py | 11 +++++ test-requirements.txt | 1 + 6 files changed, 83 insertions(+), 25 deletions(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 489e18e50529..9edd07ab945b 100755 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -3846,6 +3846,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 32833b074bd5..2fd5b2a29ce9 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 @@ -4345,32 +4347,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 7b72cefdd150..b5d3c69154fd 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 @@ -2757,6 +2759,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 2cfe95c8cbdf..ca410ae6c1d6 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 aa798edfc500..496503161706 100755 --- a/nova/virt/libvirt/driver.py +++ b/nova/virt/libvirt/driver.py @@ -3572,6 +3572,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, diff --git a/test-requirements.txt b/test-requirements.txt index 6423a69f4062..32b6f1b80d0e 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -3,6 +3,7 @@ coverage>=3.6 discover feedparser fixtures>=0.3.12 +mock>=1.0 mox==0.5.3 MySQL-python psycopg2