diff --git a/nova/compute/resource_tracker.py b/nova/compute/resource_tracker.py index 2b26ea0b4022..0444b5ba0163 100644 --- a/nova/compute/resource_tracker.py +++ b/nova/compute/resource_tracker.py @@ -31,7 +31,7 @@ from nova.compute import resources as ext_resources from nova.compute import task_states from nova.compute import vm_states from nova import exception -from nova.i18n import _, _LI, _LW +from nova.i18n import _, _LE, _LI, _LW from nova import objects from nova.objects import base as obj_base from nova.objects import migration as migration_obj @@ -491,6 +491,20 @@ class ResourceTracker(object): self._update_available_resource(context, resources) + def _pair_instances_to_migrations(self, migrations, instances): + instance_by_uuid = {inst.uuid: inst for inst in instances} + for migration in migrations: + try: + migration.instance = instance_by_uuid[migration.instance_uuid] + except KeyError: + # NOTE(danms): If this happens, we don't set it here, and + # let the code either fail or lazy-load the instance later + # which is what happened before we added this optimization. + # This _should_ not be possible, of course. + LOG.error(_LE('Migration for instance %(uuid)s refers to ' + 'another host\'s instance!'), + {'uuid': migration.instance_uuid}) + @utils.synchronized(COMPUTE_RESOURCE_SEMAPHORE) def _update_available_resource(self, context, resources): @@ -516,7 +530,8 @@ class ResourceTracker(object): instances = objects.InstanceList.get_by_host_and_node( context, self.host, self.nodename, expected_attrs=['system_metadata', - 'numa_topology']) + 'numa_topology', + 'flavor', 'migration_context']) # Now calculate usage based on instance utilization: self._update_usage_from_instances(context, instances) @@ -525,6 +540,7 @@ class ResourceTracker(object): migrations = objects.MigrationList.get_in_progress_by_host_and_node( context, self.host, self.nodename) + self._pair_instances_to_migrations(migrations, instances) self._update_usage_from_migrations(context, migrations) # Detect and account for orphaned instances that may exist on the diff --git a/nova/objects/migration.py b/nova/objects/migration.py index d23b4f86a45d..c88adf7efc63 100644 --- a/nova/objects/migration.py +++ b/nova/objects/migration.py @@ -119,7 +119,14 @@ class Migration(base.NovaPersistentObject, base.NovaObject, @property def instance(self): - return objects.Instance.get_by_uuid(self._context, self.instance_uuid) + if not hasattr(self, '_cached_instance'): + self._cached_instance = objects.Instance.get_by_uuid( + self._context, self.instance_uuid) + return self._cached_instance + + @instance.setter + def instance(self, instance): + self._cached_instance = instance @base.NovaObjectRegistry.register diff --git a/nova/tests/unit/compute/test_resource_tracker.py b/nova/tests/unit/compute/test_resource_tracker.py index e942efd2c8fe..bc9f9d57553f 100644 --- a/nova/tests/unit/compute/test_resource_tracker.py +++ b/nova/tests/unit/compute/test_resource_tracker.py @@ -40,6 +40,7 @@ from nova.objects import pci_device_pool from nova import rpc from nova import test from nova.tests.unit.pci import fakes as pci_fakes +from nova.tests import uuidsentinel from nova.virt import driver @@ -1006,19 +1007,46 @@ class InstanceClaimTestCase(BaseTrackerTestCase): "fakenode") @mock.patch('nova.objects.MigrationList.get_in_progress_by_host_and_node') - def test_instances_with_live_migrations(self, mock_migration_list): + @mock.patch('nova.objects.InstanceList.get_by_host_and_node') + def test_instances_with_live_migrations(self, mock_instance_list, + mock_migration_list): instance = self._fake_instance_obj() migration = objects.Migration(context=self.context, migration_type='live-migration', instance_uuid=instance.uuid) mock_migration_list.return_value = [migration] - self.tracker.update_available_resource(self.context) - self.assertEqual(0, self.tracker.compute_node['memory_mb_used']) - self.assertEqual(0, self.tracker.compute_node['local_gb_used']) + mock_instance_list.return_value = [instance] + with mock.patch.object(self.tracker, '_pair_instances_to_migrations' + ) as mock_pair: + self.tracker.update_available_resource(self.context) + self.assertTrue(mock_pair.called) + self.assertEqual( + instance.uuid, + mock_pair.call_args_list[0][0][0][0].instance_uuid) + self.assertEqual(instance.uuid, + mock_pair.call_args_list[0][0][1][0].uuid) + self.assertEqual( + ['system_metadata', 'numa_topology', 'flavor', + 'migration_context'], + mock_instance_list.call_args_list[0][1]['expected_attrs']) + self.assertEqual(FAKE_VIRT_MEMORY_MB + FAKE_VIRT_MEMORY_OVERHEAD, + self.tracker.compute_node['memory_mb_used']) + self.assertEqual(ROOT_GB + EPHEMERAL_GB, + self.tracker.compute_node['local_gb_used']) mock_migration_list.assert_called_once_with(self.context, "fakehost", "fakenode") + def test_pair_instances_to_migrations(self): + migrations = [objects.Migration(instance_uuid=uuidsentinel.instance1), + objects.Migration(instance_uuid=uuidsentinel.instance2)] + instances = [objects.Instance(uuid=uuidsentinel.instance2), + objects.Instance(uuid=uuidsentinel.instance1)] + self.tracker._pair_instances_to_migrations(migrations, instances) + order = [uuidsentinel.instance1, uuidsentinel.instance2] + for i, migration in enumerate(migrations): + self.assertEqual(order[i], migration.instance.uuid) + @mock.patch('nova.compute.claims.Claim') @mock.patch('nova.objects.Instance.save') def test_claim_saves_numa_topology(self, mock_save, mock_claim): diff --git a/nova/tests/unit/compute/test_tracker.py b/nova/tests/unit/compute/test_tracker.py index ac6aa310ab8e..104f6e159665 100644 --- a/nova/tests/unit/compute/test_tracker.py +++ b/nova/tests/unit/compute/test_tracker.py @@ -452,7 +452,9 @@ class TestUpdateAvailableResources(BaseTestCase): 'fake-node', expected_attrs=[ 'system_metadata', - 'numa_topology']) + 'numa_topology', + 'flavor', + 'migration_context']) get_cn_mock.assert_called_once_with(mock.sentinel.ctx, 'fake-host', 'fake-node') migr_mock.assert_called_once_with(mock.sentinel.ctx, 'fake-host', diff --git a/nova/tests/unit/objects/test_migration.py b/nova/tests/unit/objects/test_migration.py index 3ff63c735f69..3abc54421643 100644 --- a/nova/tests/unit/objects/test_migration.py +++ b/nova/tests/unit/objects/test_migration.py @@ -22,6 +22,7 @@ from nova import objects from nova.objects import migration from nova.tests.unit import fake_instance from nova.tests.unit.objects import test_objects +from nova.tests import uuidsentinel NOW = timeutils.utcnow().replace(microsecond=0) @@ -142,6 +143,16 @@ class _TestMigrationObject(object): self.mox.ReplayAll() self.assertEqual(mig.instance.host, fake_inst['host']) + def test_instance_setter(self): + migration = objects.Migration(instance_uuid=uuidsentinel.instance) + inst = objects.Instance(uuid=uuidsentinel.instance) + with mock.patch('nova.objects.Instance.get_by_uuid') as mock_get: + migration.instance = inst + migration.instance + self.assertFalse(mock_get.called) + self.assertEqual(inst, migration._cached_instance) + self.assertEqual(inst, migration.instance) + def test_get_unconfirmed_by_dest_compute(self): ctxt = context.get_admin_context() fake_migration = fake_db_migration()