From 9d5fb1b58e908ccacbbbf29341918d0b0588a36f 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 --- 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 558ba2eab7d1..5d7a415b9875 100644 --- a/nova/tests/unit/virt/ironic/test_driver.py +++ b/nova/tests/unit/virt/ironic/test_driver.py @@ -1019,8 +1019,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( @@ -1028,24 +1032,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 90d47a79d2a6..b88b7b45f607 100644 --- a/nova/virt/ironic/driver.py +++ b/nova/virt/ironic/driver.py @@ -892,11 +892,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)) properties = self._parse_node_properties(node) memory_kib = properties['memory_mb'] * 1024