From 4ede35281160b8d988ef5b775b97d81950d0c6e1 Mon Sep 17 00:00:00 2001 From: Jim Rollenhagen Date: Wed, 12 Sep 2018 12:11:30 -0600 Subject: [PATCH] ironic: stop hammering ironic API in power sync loop Use our node cache to look up the node for an instance, if we have it. If we can't find it, fall back to hitting ironic's API. This should be relatively up-to-date info, as it is refreshed on every resource tracker loop. Closes-Bug: #1793556 Change-Id: I0069cbc327d952d42dbb8fe54949faab89995a7e (cherry picked from commit 9d5fb1b58e908ccacbbbf29341918d0b0588a36f) --- nova/tests/unit/virt/ironic/test_driver.py | 79 +++++++++++++++++++++- nova/virt/ironic/driver.py | 20 ++++-- 2 files changed, 92 insertions(+), 7 deletions(-) diff --git a/nova/tests/unit/virt/ironic/test_driver.py b/nova/tests/unit/virt/ironic/test_driver.py index c6432d1df1bc..e1aa015014be 100644 --- a/nova/tests/unit/virt/ironic/test_driver.py +++ b/nova/tests/unit/virt/ironic/test_driver.py @@ -1160,8 +1160,12 @@ class IronicDriverTestCase(test.NoDBTestCase): self.assertEqual(0, mock_get.call_count) mock_nr.assert_called_once_with(node) + @mock.patch.object(ironic_driver.IronicDriver, '_get_node_list') + @mock.patch.object(objects.InstanceList, 'get_uuids_by_host') + @mock.patch.object(objects.ServiceList, 'get_all_computes_by_hv_type') @mock.patch.object(FAKE_CLIENT.node, 'get_by_instance_uuid') - def test_get_info(self, mock_gbiu): + def test_get_info(self, mock_gbiu, mock_svc_by_hv, + mock_uuids_by_host, mock_get_node_list): properties = {'memory_mb': 512, 'cpus': 2} power_state = ironic_states.POWER_ON node = _get_cached_node( @@ -1169,24 +1173,95 @@ class IronicDriverTestCase(test.NoDBTestCase): power_state=power_state) mock_gbiu.return_value = node + mock_svc_by_hv.return_value = [] + mock_get_node_list.return_value = [] # ironic_states.POWER_ON should be mapped to # nova_states.RUNNING instance = fake_instance.fake_instance_obj('fake-context', uuid=self.instance_uuid) + mock_uuids_by_host.return_value = [instance.uuid] result = self.driver.get_info(instance) self.assertEqual(hardware.InstanceInfo(state=nova_states.RUNNING), result) + mock_gbiu.assert_called_once_with(instance.uuid, + fields=ironic_driver._NODE_FIELDS) + @mock.patch.object(ironic_driver.IronicDriver, '_get_node_list') + @mock.patch.object(objects.InstanceList, 'get_uuids_by_host') + @mock.patch.object(objects.ServiceList, 'get_all_computes_by_hv_type') @mock.patch.object(FAKE_CLIENT.node, 'get_by_instance_uuid') - def test_get_info_http_not_found(self, mock_gbiu): + def test_get_info_cached(self, mock_gbiu, mock_svc_by_hv, + mock_uuids_by_host, mock_get_node_list): + properties = {'memory_mb': 512, 'cpus': 2} + power_state = ironic_states.POWER_ON + node = _get_cached_node( + instance_uuid=self.instance_uuid, properties=properties, + power_state=power_state) + + mock_gbiu.return_value = node + mock_svc_by_hv.return_value = [] + mock_get_node_list.return_value = [node] + + # ironic_states.POWER_ON should be mapped to + # nova_states.RUNNING + instance = fake_instance.fake_instance_obj('fake-context', + uuid=self.instance_uuid) + mock_uuids_by_host.return_value = [instance.uuid] + result = self.driver.get_info(instance) + self.assertEqual(hardware.InstanceInfo(state=nova_states.RUNNING), + result) + mock_gbiu.assert_not_called() + + @mock.patch.object(ironic_driver.IronicDriver, '_get_node_list') + @mock.patch.object(objects.InstanceList, 'get_uuids_by_host') + @mock.patch.object(objects.ServiceList, 'get_all_computes_by_hv_type') + @mock.patch.object(FAKE_CLIENT.node, 'get_by_instance_uuid') + def test_get_info_not_found_in_cache(self, mock_gbiu, mock_svc_by_hv, + mock_uuids_by_host, + mock_get_node_list): + properties = {'memory_mb': 512, 'cpus': 2} + power_state = ironic_states.POWER_ON + node = _get_cached_node( + instance_uuid=self.instance_uuid, properties=properties, + power_state=power_state) + node2 = _get_cached_node() + + mock_gbiu.return_value = node + mock_svc_by_hv.return_value = [] + mock_get_node_list.return_value = [node2] + + # ironic_states.POWER_ON should be mapped to + # nova_states.RUNNING + instance = fake_instance.fake_instance_obj('fake-context', + uuid=self.instance_uuid) + mock_uuids_by_host.return_value = [instance.uuid] + result = self.driver.get_info(instance) + self.assertEqual(hardware.InstanceInfo(state=nova_states.RUNNING), + result) + mock_gbiu.assert_called_once() + mock_gbiu.assert_called_once_with(instance.uuid, + fields=ironic_driver._NODE_FIELDS) + + @mock.patch.object(ironic_driver.IronicDriver, '_get_node_list') + @mock.patch.object(objects.InstanceList, 'get_uuids_by_host') + @mock.patch.object(objects.ServiceList, 'get_all_computes_by_hv_type') + @mock.patch.object(FAKE_CLIENT.node, 'get_by_instance_uuid') + def test_get_info_http_not_found(self, mock_gbiu, mock_svc_by_hv, + mock_uuids_by_host, mock_get_node_list): mock_gbiu.side_effect = ironic_exception.NotFound() + mock_svc_by_hv.return_value = [] + mock_get_node_list.return_value = [] instance = fake_instance.fake_instance_obj( self.ctx, uuid=uuidutils.generate_uuid()) + mock_uuids_by_host.return_value = [instance] + mock_uuids_by_host.return_value = [instance.uuid] result = self.driver.get_info(instance) self.assertEqual(hardware.InstanceInfo(state=nova_states.NOSTATE), result) + mock_gbiu.assert_called_once_with(instance.uuid, + fields=ironic_driver._NODE_FIELDS) @mock.patch.object(objects.Instance, 'save') @mock.patch.object(loopingcall, 'FixedIntervalLoopingCall') diff --git a/nova/virt/ironic/driver.py b/nova/virt/ironic/driver.py index d6af4f66ed09..d44c5f85b0e6 100644 --- a/nova/virt/ironic/driver.py +++ b/nova/virt/ironic/driver.py @@ -897,11 +897,21 @@ class IronicDriver(virt_driver.ComputeDriver): :param instance: the instance object. :returns: an InstanceInfo object """ - try: - node = self._validate_instance_and_node(instance) - except exception.InstanceNotFound: - return hardware.InstanceInfo( - state=map_power_state(ironic_states.NOSTATE)) + # we should already have a cache for our nodes, refreshed on every + # RT loop. but if we don't have a cache, generate it. + if not self.node_cache: + self._refresh_cache() + + for node in self.node_cache.values(): + if instance.uuid == node.instance_uuid: + break + else: + # if we can't find the instance, fall back to ironic + try: + node = self._validate_instance_and_node(instance) + except exception.InstanceNotFound: + return hardware.InstanceInfo( + state=map_power_state(ironic_states.NOSTATE)) return hardware.InstanceInfo(state=map_power_state(node.power_state))