diff --git a/nova/compute/resource_tracker.py b/nova/compute/resource_tracker.py index 79032343998c..f7f973eae12c 100644 --- a/nova/compute/resource_tracker.py +++ b/nova/compute/resource_tracker.py @@ -140,6 +140,7 @@ class ResourceTracker(object): self.stats = stats.Stats() self.tracked_instances = {} self.tracked_migrations = {} + self.is_bfv = {} # dict, keyed by instance uuid, to is_bfv boolean monitor_handler = monitors.MonitorHandler(self) self.monitors = monitor_handler.monitors self.old_resources = collections.defaultdict(objects.ComputeNode) @@ -1142,6 +1143,12 @@ class ResourceTracker(object): self._update_usage(self._get_usage_dict(instance), nodename, sign=sign) + # Stop tracking removed instances in the is_bfv cache. This needs to + # happen *after* calling _get_usage_dict() since that relies on the + # is_bfv cache. + if is_removed_instance and uuid in self.is_bfv: + del self.is_bfv[uuid] + cn.current_workload = self.stats.calculate_workload() if self.pci_tracker: obj = self.pci_tracker.stats.to_device_pools_obj() @@ -1459,8 +1466,13 @@ class ResourceTracker(object): """ usage = {} if isinstance(object_or_dict, objects.Instance): - is_bfv = compute_utils.is_volume_backed_instance( - object_or_dict._context, object_or_dict) + # Check to see if we have the is_bfv value cached. + if object_or_dict.uuid in self.is_bfv: + is_bfv = self.is_bfv[object_or_dict.uuid] + else: + is_bfv = compute_utils.is_volume_backed_instance( + object_or_dict._context, object_or_dict) + self.is_bfv[object_or_dict.uuid] = is_bfv usage = {'memory_mb': object_or_dict.flavor.memory_mb, 'swap': object_or_dict.flavor.swap, 'vcpus': object_or_dict.flavor.vcpus, diff --git a/nova/tests/unit/compute/test_resource_tracker.py b/nova/tests/unit/compute/test_resource_tracker.py index 303662524fee..18baaa772a03 100644 --- a/nova/tests/unit/compute/test_resource_tracker.py +++ b/nova/tests/unit/compute/test_resource_tracker.py @@ -2680,9 +2680,22 @@ class TestUpdateUsageFromInstance(BaseTestCase): @mock.patch('nova.compute.utils.is_volume_backed_instance') def test_get_usage_dict_return_0_root_gb_for_bfv_instance( self, mock_check_bfv): - mock_check_bfv.retur_value = True + mock_check_bfv.return_value = True + # Make sure the cache is empty. + self.assertNotIn(self.instance.uuid, self.rt.is_bfv) result = self.rt._get_usage_dict(self.instance) self.assertEqual(0, result['root_gb']) + mock_check_bfv.assert_called_once_with( + self.instance._context, self.instance) + # Make sure we updated the cache. + self.assertIn(self.instance.uuid, self.rt.is_bfv) + self.assertTrue(self.rt.is_bfv[self.instance.uuid]) + # Now run _get_usage_dict again to make sure we don't call + # is_volume_backed_instance. + mock_check_bfv.reset_mock() + result = self.rt._get_usage_dict(self.instance) + self.assertEqual(0, result['root_gb']) + mock_check_bfv.assert_not_called() @mock.patch('nova.compute.utils.is_volume_backed_instance') def test_get_usage_dict_include_swap( @@ -2711,12 +2724,16 @@ class TestUpdateUsageFromInstance(BaseTestCase): def test_shelve_offloading(self, mock_update_usage, mock_check_bfv): mock_check_bfv.return_value = False self.instance.vm_state = vm_states.SHELVED_OFFLOADED + # Stub out the is_bfv cache to make sure we remove the instance + # from it after updating usage. + self.rt.is_bfv[self.instance.uuid] = False self.rt.tracked_instances = { self.instance.uuid: obj_base.obj_to_primitive(self.instance) } self.rt._update_usage_from_instance(mock.sentinel.ctx, self.instance, _NODENAME) - + # The instance should have been removed from the is_bfv cache. + self.assertNotIn(self.instance.uuid, self.rt.is_bfv) mock_update_usage.assert_called_once_with( self.rt._get_usage_dict(self.instance), _NODENAME, sign=-1)