diff --git a/doc/notification_samples/instance-live_migration_rollback-start.json b/doc/notification_samples/instance-live_migration_rollback-start.json index 5bffa6057e68..148958d3d590 100644 --- a/doc/notification_samples/instance-live_migration_rollback-start.json +++ b/doc/notification_samples/instance-live_migration_rollback-start.json @@ -3,7 +3,8 @@ "payload":{ "$ref":"common_payloads/InstanceActionPayload.json#", "nova_object.data":{ - "action_initiator_user": "admin" + "action_initiator_user": "admin", + "task_state": "migrating" } }, "priority":"INFO", diff --git a/doc/notification_samples/instance-live_migration_rollback_dest-end.json b/doc/notification_samples/instance-live_migration_rollback_dest-end.json index fe36e55be32f..745c6990caeb 100644 --- a/doc/notification_samples/instance-live_migration_rollback_dest-end.json +++ b/doc/notification_samples/instance-live_migration_rollback_dest-end.json @@ -3,7 +3,8 @@ "payload": { "$ref": "common_payloads/InstanceActionPayload.json#", "nova_object.data": { - "action_initiator_user": "admin" + "action_initiator_user": "admin", + "task_state": "migrating" } }, "priority": "INFO", diff --git a/doc/notification_samples/instance-live_migration_rollback_dest-start.json b/doc/notification_samples/instance-live_migration_rollback_dest-start.json index 422f7914d355..32858d325269 100644 --- a/doc/notification_samples/instance-live_migration_rollback_dest-start.json +++ b/doc/notification_samples/instance-live_migration_rollback_dest-start.json @@ -3,7 +3,8 @@ "payload": { "$ref": "common_payloads/InstanceActionPayload.json#", "nova_object.data": { - "action_initiator_user": "admin" + "action_initiator_user": "admin", + "task_state": "migrating" } }, "priority": "INFO", diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 1113f0b5ef7a..1b13d13bd68d 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -7663,12 +7663,14 @@ class ComputeManager(manager.Manager): migration) LOG.debug('destination check data is %s', dest_check_data) try: + allocs = self.reportclient.get_allocations_for_consumer( + ctxt, instance.uuid) migrate_data = self.compute_rpcapi.check_can_live_migrate_source( ctxt, instance, dest_check_data) if ('src_supports_numa_live_migration' in migrate_data and migrate_data.src_supports_numa_live_migration): migrate_data = self._live_migration_claim( - ctxt, instance, migrate_data, migration, limits) + ctxt, instance, migrate_data, migration, limits, allocs) elif 'dst_supports_numa_live_migration' in dest_check_data: LOG.info('Destination was ready for NUMA live migration, ' 'but source is either too old, or is set to an ' @@ -7688,7 +7690,7 @@ class ComputeManager(manager.Manager): return migrate_data def _live_migration_claim(self, ctxt, instance, migrate_data, - migration, limits): + migration, limits, allocs): """Runs on the destination and does a resources claim, if necessary. Currently, only NUMA live migrations require it. @@ -7707,7 +7709,7 @@ class ComputeManager(manager.Manager): # migration.dest_node here and must use self._get_nodename(). claim = self.rt.live_migration_claim( ctxt, instance, self._get_nodename(instance), migration, - limits) + limits, allocs) LOG.debug('Created live migration claim.', instance=instance) except exception.ComputeResourcesUnavailable as e: raise exception.MigrationPreCheckError( @@ -8424,6 +8426,17 @@ class ComputeManager(manager.Manager): # destination, which will update it source_node = instance.node + do_cleanup, destroy_disks = self._live_migration_cleanup_flags( + migrate_data) + + if do_cleanup: + LOG.debug('Calling driver.cleanup from _post_live_migration', + instance=instance) + self.driver.cleanup(ctxt, instance, unplug_nw_info, + destroy_disks=destroy_disks, + migrate_data=migrate_data, + destroy_vifs=destroy_vifs) + # Define domain at destination host, without doing it, # pause/suspend/terminate do not work. post_at_dest_success = True @@ -8438,26 +8451,6 @@ class ComputeManager(manager.Manager): LOG.exception("Post live migration at destination %s failed", dest, instance=instance, error=error) - do_cleanup, destroy_disks = self._live_migration_cleanup_flags( - migrate_data) - - if do_cleanup: - # NOTE(artom) By this time post_live_migration_at_destination() - # will have applied the migration context and saved the instance, - # writing a new instance NUMA topology in the process (if the - # intance has one). Here on the source, some drivers will call - # instance.save() in their cleanup() method, which would clobber - # the new instance NUMA topology saved by the destination with the - # old fields in our instance object. To prevent this, refresh our - # instance. - instance.refresh() - LOG.debug('Calling driver.cleanup from _post_live_migration', - instance=instance) - self.driver.cleanup(ctxt, instance, unplug_nw_info, - destroy_disks=destroy_disks, - migrate_data=migrate_data, - destroy_vifs=destroy_vifs) - self.instance_events.clear_events_for_instance(instance) # NOTE(timello): make sure we update available resources on source @@ -8705,28 +8698,6 @@ class ComputeManager(manager.Manager): 'rollback; compute driver did not provide migrate_data', instance=instance) - # TODO(artom) drop_move_claim_at_destination() is new in RPC 5.3, only - # call it if we performed a NUMA-aware live migration (which implies us - # being able to send RPC 5.3). To check this, we can use the - # src_supports_numa_live_migration flag, as it will be set if and only - # if: - # - dst_supports_numa_live_migration made its way to the source - # (meaning both dest and source are new and conductor can speak - # RPC 5.3) - # - src_supports_numa_live_migration was set by the source driver and - # passed the send-RPC-5.3 check. - # This check can be removed in RPC 6.0. - if ('src_supports_numa_live_migration' in migrate_data and - migrate_data.src_supports_numa_live_migration): - LOG.debug('Calling destination to drop move claim.', - instance=instance) - self.compute_rpcapi.drop_move_claim_at_destination(context, - instance, dest) - instance.task_state = None - instance.progress = 0 - instance.drop_migration_context() - instance.save(expected_task_state=[task_states.MIGRATING]) - # NOTE(tr3buchet): setup networks on source host (really it's re-setup # for nova-network) # NOTE(mriedem): This is a no-op for neutron. @@ -8785,6 +8756,34 @@ class ComputeManager(manager.Manager): 'during live migration rollback.', instance=instance) + # NOTE(luyao): We drop move_claim and migration_context after cleanup + # is complete, to ensure the specific resources claimed on destination + # are released safely. + # TODO(artom) drop_move_claim_at_destination() is new in RPC 5.3, only + # call it if we performed a NUMA-aware live migration (which implies us + # being able to send RPC 5.3). To check this, we can use the + # src_supports_numa_live_migration flag, as it will be set if and only + # if: + # - dst_supports_numa_live_migration made its way to the source + # (meaning both dest and source are new and conductor can speak + # RPC 5.3) + # - src_supports_numa_live_migration was set by the source driver and + # passed the send-RPC-5.3 check. + # This check can be removed in RPC 6.0. + if ('src_supports_numa_live_migration' in migrate_data and + migrate_data.src_supports_numa_live_migration): + LOG.debug('Calling destination to drop move claim.', + instance=instance) + self.compute_rpcapi.drop_move_claim_at_destination(context, + instance, dest) + + # NOTE(luyao): We only update instance info after rollback operations + # are complete + instance.task_state = None + instance.progress = 0 + instance.drop_migration_context() + instance.save(expected_task_state=[task_states.MIGRATING]) + self._notify_about_instance_usage(context, instance, "live_migration._rollback.end") compute_utils.notify_about_instance_action(context, instance, @@ -8793,6 +8792,9 @@ class ComputeManager(manager.Manager): phase=fields.NotificationPhase.END, bdms=bdms) + # TODO(luyao): set migration status to 'failed' but not 'error' + # which means rollback_live_migration is done, we have successfully + # cleaned up and returned instance back to normal status. self._set_migration_status(migration, migration_status) @wrap_exception() @@ -8865,9 +8867,10 @@ class ComputeManager(manager.Manager): # check_can_live_migrate_destination() self.rt.free_pci_device_claims_for_instance(context, instance) - self.driver.rollback_live_migration_at_destination( - context, instance, network_info, block_device_info, - destroy_disks=destroy_disks, migrate_data=migrate_data) + with instance.mutated_migration_context(): + self.driver.rollback_live_migration_at_destination( + context, instance, network_info, block_device_info, + destroy_disks=destroy_disks, migrate_data=migrate_data) self._notify_about_instance_usage( context, instance, "live_migration.rollback.dest.end", diff --git a/nova/compute/resource_tracker.py b/nova/compute/resource_tracker.py index 030de18cc6a6..63496e7a0b4a 100644 --- a/nova/compute/resource_tracker.py +++ b/nova/compute/resource_tracker.py @@ -209,7 +209,7 @@ class ResourceTracker(object): @utils.synchronized(COMPUTE_RESOURCE_SEMAPHORE, fair=True) def live_migration_claim(self, context, instance, nodename, migration, - limits): + limits, allocs): """Builds a MoveClaim for a live migration. :param context: The request context. @@ -219,15 +219,14 @@ class ResourceTracker(object): migration. :param limits: A SchedulerLimits object from when the scheduler selected the destination host. + :param allocs: The placement allocation records for the instance. :returns: A MoveClaim for this live migration. """ # Flavor and image cannot change during a live migration. instance_type = instance.flavor image_meta = instance.image_meta - # TODO(Luyao) will pass allocations to live_migration_claim after the - # live migration change is done, now just set it None to _move_claim return self._move_claim(context, instance, instance_type, nodename, - migration, None, move_type='live-migration', + migration, allocs, move_type='live-migration', image_meta=image_meta, limits=limits) def _move_claim(self, context, instance, new_instance_type, nodename, diff --git a/nova/conductor/tasks/live_migrate.py b/nova/conductor/tasks/live_migrate.py index acea49228e78..5aa701e688af 100644 --- a/nova/conductor/tasks/live_migrate.py +++ b/nova/conductor/tasks/live_migrate.py @@ -12,6 +12,7 @@ from oslo_log import log as logging import oslo_messaging as messaging +from oslo_utils import excutils import six from nova import availability_zones @@ -93,12 +94,12 @@ class LiveMigrationTask(base.TaskBase): # live migrating with a specific destination host so the scheduler # is bypassed. There are still some minimal checks performed here # though. - source_node, dest_node = self._check_requested_destination() - # Now that we're semi-confident in the force specified host, we - # need to copy the source compute node allocations in Placement - # to the destination compute node. Normally select_destinations() - # in the scheduler would do this for us, but when forcing the - # target host we don't call the scheduler. + self._check_destination_is_not_source() + self._check_host_is_up(self.destination) + self._check_destination_has_enough_memory() + source_node, dest_node = ( + self._check_compatible_with_source_hypervisor( + self.destination)) # TODO(mriedem): Call select_destinations() with a # skip_filters=True flag so the scheduler does the work of claiming # resources on the destination in Placement but still bypass the @@ -111,11 +112,20 @@ class LiveMigrationTask(base.TaskBase): # this assumption fails then placement will return consumer # generation conflict and this call raise a AllocationUpdateFailed # exception. We let that propagate here to abort the migration. + # NOTE(luyao): When forcing the target host we don't call the + # scheduler, that means we need to get allocations from placement + # first, then claim resources in resource tracker on the + # destination host based on these allocations. scheduler_utils.claim_resources_on_destination( self.context, self.report_client, self.instance, source_node, dest_node, source_allocations=self._held_allocations, consumer_generation=None) + try: + self._check_requested_destination() + except Exception: + with excutils.save_and_reraise_exception(): + self._remove_host_allocations(dest_node.uuid) # dest_node is a ComputeNode object, so we need to get the actual # node name off it to set in the Migration object below. @@ -264,15 +274,7 @@ class LiveMigrationTask(base.TaskBase): raise exception.ComputeServiceUnavailable(host=host) def _check_requested_destination(self): - """Performs basic pre-live migration checks for the forced host. - - :returns: tuple of (source ComputeNode, destination ComputeNode) - """ - self._check_destination_is_not_source() - self._check_host_is_up(self.destination) - self._check_destination_has_enough_memory() - source_node, dest_node = self._check_compatible_with_source_hypervisor( - self.destination) + """Performs basic pre-live migration checks for the forced host.""" # NOTE(gibi): This code path is used when the live migration is forced # to a target host and skipping the scheduler. Such operation is # rejected for servers with nested resource allocations since @@ -289,7 +291,6 @@ class LiveMigrationTask(base.TaskBase): raise exception.MigrationPreCheckError( reason=(_('Unable to force live migrate instance %s ' 'across cells.') % self.instance.uuid)) - return source_node, dest_node def _check_destination_is_not_source(self): if self.destination == self.source: diff --git a/nova/tests/functional/regressions/test_bug_1595962.py b/nova/tests/functional/regressions/test_bug_1595962.py index daf13871b4f0..2695870a442d 100644 --- a/nova/tests/functional/regressions/test_bug_1595962.py +++ b/nova/tests/functional/regressions/test_bug_1595962.py @@ -73,6 +73,7 @@ class TestSerialConsoleLiveMigrate(test.TestCase): self.image_id = self.api.get_images()[0]['id'] self.flavor_id = self.api.get_flavors()[0]['id'] + @mock.patch.object(fakelibvirt.Domain, 'undefine') @mock.patch('nova.virt.libvirt.LibvirtDriver.get_volume_connector') @mock.patch('nova.virt.libvirt.guest.Guest.get_job_info') @mock.patch.object(fakelibvirt.Domain, 'migrateToURI3') @@ -100,7 +101,8 @@ class TestSerialConsoleLiveMigrate(test.TestCase): mock_host_get_connection, mock_migrate_to_uri, mock_get_job_info, - mock_get_volume_connector): + mock_get_volume_connector, + mock_undefine): """Regression test for bug #1595962. If the graphical consoles VNC and SPICE are disabled, the @@ -120,6 +122,12 @@ class TestSerialConsoleLiveMigrate(test.TestCase): version=fakelibvirt.FAKE_LIBVIRT_VERSION, hv_version=fakelibvirt.FAKE_QEMU_VERSION) mock_host_get_connection.return_value = fake_connection + # We invoke cleanup on source host first which will call undefine + # method currently. Since in functional test we make all compute + # services linked to the same connection, we need to mock the undefine + # method to avoid triggering 'Domain not found' error in subsequent + # rpc call post_live_migration_at_destination. + mock_undefine.return_value = True server_attr = dict(name='server1', imageRef=self.image_id, diff --git a/nova/tests/unit/compute/test_compute_mgr.py b/nova/tests/unit/compute/test_compute_mgr.py index 4020d742a75d..aef8618bc195 100644 --- a/nova/tests/unit/compute/test_compute_mgr.py +++ b/nova/tests/unit/compute/test_compute_mgr.py @@ -3058,10 +3058,10 @@ class ComputeManagerUnitTestCase(test.NoDBTestCase, post_claim_md, self.compute._live_migration_claim( self.context, instance, md, migration, - mock.sentinel.limits)) + mock.sentinel.limits, None)) mock_lm_claim.assert_called_once_with( self.context, instance, 'fake-dest-node', migration, - mock.sentinel.limits) + mock.sentinel.limits, None) mock_post_claim_migrate_data.assert_called_once_with( self.context, instance, md, mock_claim) @@ -3086,10 +3086,10 @@ class ComputeManagerUnitTestCase(test.NoDBTestCase, exception.MigrationPreCheckError, self.compute._live_migration_claim, self.context, instance, objects.LibvirtLiveMigrateData(), - migration, mock.sentinel.limits) + migration, mock.sentinel.limits, None) mock_lm_claim.assert_called_once_with( self.context, instance, 'fake-dest-node', migration, - mock.sentinel.limits) + mock.sentinel.limits, None) mock_get_nodename.assert_called_once_with(instance) mock_post_claim_migrate_data.assert_not_called() @@ -3230,7 +3230,7 @@ class ComputeManagerUnitTestCase(test.NoDBTestCase, 'Destination was ready for NUMA live migration')) else: mock_lm_claim.assert_called_once_with( - self.context, instance, mig_data, migration, limits) + self.context, instance, mig_data, migration, limits, None) self.assertEqual(post_claim_md, result) mock_check_clean.assert_called_once_with(self.context, dest_check_data) @@ -9581,13 +9581,11 @@ class ComputeManagerMigrationTestCase(test.NoDBTestCase, @mock.patch.object(self.compute, 'compute_rpcapi') @mock.patch.object(self.compute, '_notify_about_instance_usage') @mock.patch.object(self.compute, 'network_api') - @mock.patch.object(objects.Instance, 'refresh') - def _do_call(refresh, nwapi, notify, rpc, update): + def _do_call(nwapi, notify, rpc, update): bdms = objects.BlockDeviceMappingList(objects=[]) result = self.compute._post_live_migration( self.context, self.instance, 'foo', *args, source_bdms=bdms, **kwargs) - refresh.assert_called_once_with() return result mock_rt = self._mock_rt() @@ -9742,8 +9740,7 @@ class ComputeManagerMigrationTestCase(test.NoDBTestCase, @mock.patch.object(self.compute, 'update_available_resource') @mock.patch.object(self.compute, '_update_scheduler_instance_info') @mock.patch.object(self.compute, '_clean_instance_console_tokens') - @mock.patch.object(objects.Instance, 'refresh') - def _test(_refresh, _clean_instance_console_tokens, + def _test(_clean_instance_console_tokens, _update_scheduler_instance_info, update_available_resource, driver_cleanup, _live_migration_cleanup_flags, post_live_migration_at_destination, @@ -9757,7 +9754,6 @@ class ComputeManagerMigrationTestCase(test.NoDBTestCase, post_live_migration_at_source.assert_called_once_with( self.context, self.instance, test.MatchType(network_model.NetworkInfo)) - _refresh.assert_called_once_with() driver_cleanup.assert_called_once_with( self.context, self.instance, test.MatchType(network_model.NetworkInfo), destroy_disks=False, diff --git a/nova/tests/unit/compute/test_resource_tracker.py b/nova/tests/unit/compute/test_resource_tracker.py index 9af5b5298b8f..26e153306e58 100644 --- a/nova/tests/unit/compute/test_resource_tracker.py +++ b/nova/tests/unit/compute/test_resource_tracker.py @@ -3032,7 +3032,8 @@ class TestLiveMigration(BaseTestCase): ) as (mock_from_instance, mock_migration_save, mock_instance_save, mock_update, mock_pci_claim_instance, mock_update_usage): claim = self.rt.live_migration_claim(ctxt, instance, _NODENAME, - migration, limits=None) + migration, limits=None, + allocs=None) self.assertEqual(42, claim.migration.id) # Check that we didn't set the status to 'pre-migrating', like we # do for cold migrations, but which doesn't exist for live diff --git a/nova/tests/unit/conductor/tasks/test_live_migrate.py b/nova/tests/unit/conductor/tasks/test_live_migrate.py index acf282c8de56..127bc8a76613 100644 --- a/nova/tests/unit/conductor/tasks/test_live_migrate.py +++ b/nova/tests/unit/conductor/tasks/test_live_migrate.py @@ -97,23 +97,32 @@ class LiveMigrationTaskTestCase(test.NoDBTestCase): dest_node = objects.ComputeNode(hypervisor_hostname='dest_node') with test.nested( mock.patch.object(self.task, '_check_host_is_up'), - mock.patch.object(self.task, '_check_requested_destination', - return_value=(mock.sentinel.source_node, - dest_node)), + mock.patch.object(self.task, '_check_requested_destination'), mock.patch.object(scheduler_utils, 'claim_resources_on_destination'), mock.patch.object(self.migration, 'save'), mock.patch.object(self.task.compute_rpcapi, 'live_migration'), mock.patch('nova.conductor.tasks.migrate.' 'replace_allocation_with_migration'), + mock.patch.object(self.task, '_check_destination_is_not_source'), + mock.patch.object(self.task, + '_check_destination_has_enough_memory'), + mock.patch.object(self.task, + '_check_compatible_with_source_hypervisor', + return_value=(mock.sentinel.source_node, + dest_node)), ) as (mock_check_up, mock_check_dest, mock_claim, mock_save, mock_mig, - m_alloc): + m_alloc, m_check_diff, m_check_enough_mem, m_check_compatible): mock_mig.return_value = "bob" m_alloc.return_value = (mock.MagicMock(), mock.sentinel.allocs) self.assertEqual("bob", self.task.execute()) - mock_check_up.assert_called_once_with(self.instance_host) + mock_check_up.assert_has_calls([ + mock.call(self.instance_host), mock.call(self.destination)]) mock_check_dest.assert_called_once_with() + m_check_diff.assert_called_once() + m_check_enough_mem.assert_called_once() + m_check_compatible.assert_called_once() allocs = mock.sentinel.allocs mock_claim.assert_called_once_with( self.context, self.task.report_client, @@ -283,61 +292,16 @@ class LiveMigrationTaskTestCase(test.NoDBTestCase): self.assertRaises(exception.ComputeHostNotFound, self.task._check_host_is_up, "host") - @mock.patch.object(objects.Service, 'get_by_compute_host') - @mock.patch.object(live_migrate.LiveMigrationTask, '_get_compute_info') - @mock.patch.object(servicegroup.API, 'service_is_up') - @mock.patch.object(compute_rpcapi.ComputeAPI, - 'check_can_live_migrate_destination') - def test_check_requested_destination(self, mock_check, mock_is_up, - mock_get_info, mock_get_host): - mock_get_host.return_value = "service" - mock_is_up.return_value = True - hypervisor_details = objects.ComputeNode( - hypervisor_type="a", - hypervisor_version=6.1, - free_ram_mb=513, - memory_mb=512, - ram_allocation_ratio=1.0) - mock_get_info.return_value = hypervisor_details - mock_check.return_value = "migrate_data" - self.task.limits = fake_limits1 - - with test.nested( - mock.patch.object(self.task.network_api, - 'supports_port_binding_extension', - return_value=False), - mock.patch.object(self.task, '_check_can_migrate_pci')): - self.assertEqual((hypervisor_details, hypervisor_details), - self.task._check_requested_destination()) - self.assertEqual("migrate_data", self.task.migrate_data) - mock_get_host.assert_called_once_with(self.context, self.destination) - mock_is_up.assert_called_once_with("service") - self.assertEqual([mock.call(self.destination), - mock.call(self.instance_host), - mock.call(self.destination)], - mock_get_info.call_args_list) - mock_check.assert_called_once_with(self.context, self.instance, - self.destination, self.block_migration, self.disk_over_commit, - self.task.migration, fake_limits1) - - def test_check_requested_destination_fails_with_same_dest(self): + def test_check_destination_fails_with_same_dest(self): self.task.destination = "same" self.task.source = "same" self.assertRaises(exception.UnableToMigrateToSelf, - self.task._check_requested_destination) + self.task._check_destination_is_not_source) - @mock.patch.object(objects.Service, 'get_by_compute_host', - side_effect=exception.ComputeHostNotFound(host='host')) - def test_check_requested_destination_fails_when_destination_is_up(self, - mock): - self.assertRaises(exception.ComputeHostNotFound, - self.task._check_requested_destination) - - @mock.patch.object(live_migrate.LiveMigrationTask, '_check_host_is_up') @mock.patch.object(objects.ComputeNode, 'get_first_node_by_host_for_old_compat') - def test_check_requested_destination_fails_with_not_enough_memory( - self, mock_get_first, mock_is_up): + def test_check_destination_fails_with_not_enough_memory( + self, mock_get_first): mock_get_first.return_value = ( objects.ComputeNode(free_ram_mb=513, memory_mb=1024, @@ -347,47 +311,55 @@ class LiveMigrationTaskTestCase(test.NoDBTestCase): # ratio reduces the total available RAM to 410MB # (1024 * 0.9 - (1024 - 513)) self.assertRaises(exception.MigrationPreCheckError, - self.task._check_requested_destination) - mock_is_up.assert_called_once_with(self.destination) + self.task._check_destination_has_enough_memory) mock_get_first.assert_called_once_with(self.context, self.destination) - @mock.patch.object(live_migrate.LiveMigrationTask, '_check_host_is_up') - @mock.patch.object(live_migrate.LiveMigrationTask, - '_check_destination_has_enough_memory') @mock.patch.object(live_migrate.LiveMigrationTask, '_get_compute_info') - def test_check_requested_destination_fails_with_hypervisor_diff( - self, mock_get_info, mock_check, mock_is_up): + def test_check_compatible_fails_with_hypervisor_diff( + self, mock_get_info): mock_get_info.side_effect = [ objects.ComputeNode(hypervisor_type='b'), objects.ComputeNode(hypervisor_type='a')] self.assertRaises(exception.InvalidHypervisorType, - self.task._check_requested_destination) - mock_is_up.assert_called_once_with(self.destination) - mock_check.assert_called_once_with() + self.task._check_compatible_with_source_hypervisor, + self.destination) self.assertEqual([mock.call(self.instance_host), mock.call(self.destination)], mock_get_info.call_args_list) - @mock.patch.object(live_migrate.LiveMigrationTask, '_check_host_is_up') - @mock.patch.object(live_migrate.LiveMigrationTask, - '_check_destination_has_enough_memory') @mock.patch.object(live_migrate.LiveMigrationTask, '_get_compute_info') - def test_check_requested_destination_fails_with_hypervisor_too_old( - self, mock_get_info, mock_check, mock_is_up): + def test_check_compatible_fails_with_hypervisor_too_old( + self, mock_get_info): host1 = {'hypervisor_type': 'a', 'hypervisor_version': 7} host2 = {'hypervisor_type': 'a', 'hypervisor_version': 6} mock_get_info.side_effect = [objects.ComputeNode(**host1), objects.ComputeNode(**host2)] self.assertRaises(exception.DestinationHypervisorTooOld, - self.task._check_requested_destination) - mock_is_up.assert_called_once_with(self.destination) - mock_check.assert_called_once_with() + self.task._check_compatible_with_source_hypervisor, + self.destination) self.assertEqual([mock.call(self.instance_host), mock.call(self.destination)], mock_get_info.call_args_list) + @mock.patch.object(compute_rpcapi.ComputeAPI, + 'check_can_live_migrate_destination') + def test_check_requested_destination(self, mock_check): + mock_check.return_value = "migrate_data" + self.task.limits = fake_limits1 + + with test.nested( + mock.patch.object(self.task.network_api, + 'supports_port_binding_extension', + return_value=False), + mock.patch.object(self.task, '_check_can_migrate_pci')): + self.assertIsNone(self.task._check_requested_destination()) + self.assertEqual("migrate_data", self.task.migrate_data) + mock_check.assert_called_once_with(self.context, self.instance, + self.destination, self.block_migration, self.disk_over_commit, + self.task.migration, fake_limits1) + @mock.patch.object(objects.Service, 'get_by_compute_host') @mock.patch.object(live_migrate.LiveMigrationTask, '_get_compute_info') @mock.patch.object(servicegroup.API, 'service_is_up') diff --git a/nova/tests/unit/fake_instance.py b/nova/tests/unit/fake_instance.py index 5de52d339f05..6de88819adde 100644 --- a/nova/tests/unit/fake_instance.py +++ b/nova/tests/unit/fake_instance.py @@ -140,6 +140,7 @@ def fake_instance_obj(context, obj_instance_class=None, **updates): inst.old_flavor = None inst.new_flavor = None inst.resources = None + inst.migration_context = None inst.obj_reset_changes() return inst