From c03021fee25f47ccd2c04e0d91341cd829c9e600 Mon Sep 17 00:00:00 2001 From: cid Date: Fri, 4 Apr 2025 17:08:12 +0100 Subject: [PATCH] Remove eventlet from Ironic Python Agent This change removes several usages of eventlet from IPA: - Upgrades all requirements on oslo library versions to new ones that support non-eventlet use. - Removes use of the eventlet wsgi server (via oslo_service.wsgi) and replaces it with the cheroot wsgi server. - Removes explicit patching of python modules with eventlet Note that due to some oslo libraries still using ``eventlet`` to detect and workaround it's use. This means that it is still installed in environments alongside IPA, even if it's not used or patched into any modules. Depends-On: https://review.opendev.org/c/openstack/requirements/+/947727 Change-Id: I9accab2d5e9529a88ef5d3db85e76901f14114eb --- ironic_python_agent/__init__.py | 21 -- ironic_python_agent/agent.py | 5 +- ironic_python_agent/api/app.py | 52 ++-- ironic_python_agent/tests/unit/test_agent.py | 225 +++++------------- .../eventlet-removal-1bd8c6c2d5f8a765.yaml | 6 + requirements.txt | 12 +- 6 files changed, 109 insertions(+), 212 deletions(-) create mode 100644 releasenotes/notes/eventlet-removal-1bd8c6c2d5f8a765.yaml diff --git a/ironic_python_agent/__init__.py b/ironic_python_agent/__init__.py index c059101a9..e69de29bb 100644 --- a/ironic_python_agent/__init__.py +++ b/ironic_python_agent/__init__.py @@ -1,21 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import eventlet -import os - -os.environ["EVENTLET_NO_GREENDNS"] = "yes" - -# NOTE(JayF) Without monkey_patching socket, API requests will hang with TLS -# enabled. Enabling more than just socket for monkey patching causes failures -# in image streaming. In an ideal world, we track down all those errors and -# monkey patch everything as suggested in eventlet documentation. -eventlet.monkey_patch(all=False, socket=True) diff --git a/ironic_python_agent/agent.py b/ironic_python_agent/agent.py index e1fbbb3bd..becd1c7dd 100644 --- a/ironic_python_agent/agent.py +++ b/ironic_python_agent/agent.py @@ -22,7 +22,6 @@ import threading import time from urllib import parse as urlparse -import eventlet from oslo_concurrency import processutils from oslo_config import cfg from oslo_log import log @@ -106,7 +105,7 @@ class IronicPythonAgentHeartbeater(threading.Thread): self.agent.set_agent_advertise_addr() while self._run_next(): - eventlet.sleep(0) + time.sleep(0.1) def _run_next(self): # The logic here makes sure we don't wait exactly 5 seconds more or @@ -438,7 +437,7 @@ class IronicPythonAgent(base.ExecuteCommandMixin): self.heartbeater.start() try: while self.serve_api: - eventlet.sleep(0.1) + time.sleep(0.1) except KeyboardInterrupt: LOG.info('Caught keyboard interrupt, exiting') self.api.stop() diff --git a/ironic_python_agent/api/app.py b/ironic_python_agent/api/app.py index 6782ef915..de6965d88 100644 --- a/ironic_python_agent/api/app.py +++ b/ironic_python_agent/api/app.py @@ -13,9 +13,11 @@ # limitations under the License. import json +import threading +from cheroot.ssl import builtin +from cheroot import wsgi from oslo_log import log -from oslo_service import wsgi import werkzeug from werkzeug import exceptions as http_exc from werkzeug import routing @@ -92,7 +94,7 @@ class Application(object): :param conf: configuration object. """ self.agent = agent - self.service = None + self.server = None self._conf = conf self.url_map = routing.Map([ routing.Rule('/', endpoint='root', methods=['GET']), @@ -128,28 +130,44 @@ class Application(object): def start(self, tls_cert_file=None, tls_key_file=None): """Start the API service in the background.""" - if tls_cert_file and tls_key_file: - self._conf.set_override('cert_file', tls_cert_file, group='ssl') - self._conf.set_override('key_file', tls_key_file, group='ssl') - use_tls = True - else: - use_tls = self._conf.listen_tls - self.service = wsgi.Server(self._conf, 'ironic-python-agent', app=self, - host=self.agent.listen_address.hostname, - port=self.agent.listen_address.port, - use_ssl=use_tls) - self.service.start() + ssl_group = getattr(self._conf, 'ssl', {}) + + self.tls_cert_file = tls_cert_file or getattr( + ssl_group, 'cert_file', None) + self.tls_key_file = tls_key_file or getattr( + ssl_group, 'key_file', None) + + bind_addr = (self.agent.listen_address.hostname, + self.agent.listen_address.port) + + server = wsgi.Server(bind_addr=bind_addr, wsgi_app=self, + server_name='ironic-python-agent') + + if self.tls_cert_file and self.tls_key_file: + server.ssl_adapter = builtin.BuiltinSSLAdapter( + certificate=self.tls_cert_file, + private_key=self.tls_key_file + ) + + self.server = server + self.server.prepare() + self.server_thread = threading.Thread(target=self.server.serve) + self.server_thread.daemon = True + self.server_thread.start() + LOG.info('Started API service on port %s', self.agent.listen_address.port) def stop(self): """Stop the API service.""" LOG.debug("Stopping the API service.") - if self.service is None: - return - self.service.stop() - self.service = None + + if self.server: + self.server.stop() + self.server_thread.join(timeout=2) + self.server = None + LOG.info('Stopped API service on port %s', self.agent.listen_address.port) diff --git a/ironic_python_agent/tests/unit/test_agent.py b/ironic_python_agent/tests/unit/test_agent.py index ced7c3915..eb009c104 100644 --- a/ironic_python_agent/tests/unit/test_agent.py +++ b/ironic_python_agent/tests/unit/test_agent.py @@ -239,18 +239,14 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest): @mock.patch.object(hardware, 'dispatch_to_managers', autospec=True) @mock.patch.object(agent.IronicPythonAgent, '_wait_for_interface', autospec=True) - @mock.patch('oslo_service.wsgi.Server', autospec=True) @mock.patch.object(hardware, 'get_managers', autospec=True) - def test_run(self, mock_get_managers, mock_wsgi, - mock_wait, mock_dispatch): + def test_run(self, mock_get_managers, mock_wait, mock_dispatch): CONF.set_override('inspection_callback_url', '') - wsgi_server = mock_wsgi.return_value - - def set_serve_api(): + def set_serve_api(*args, **kwargs): self.agent.serve_api = False - wsgi_server.start.side_effect = set_serve_api + self.agent.api.start = mock.Mock(side_effect=set_serve_api) self.agent.heartbeater = mock.Mock() self.agent.api_client.lookup_node = mock.Mock() self.agent.api_client.lookup_node.return_value = { @@ -265,11 +261,7 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest): self.agent.run() - mock_wsgi.assert_called_once_with(CONF, 'ironic-python-agent', - app=self.agent.api, - host=mock.ANY, port=9999, - use_ssl=False) - wsgi_server.start.assert_called_once_with() + self.agent.api.start.assert_called_once_with(mock.ANY, mock.ANY) mock_wait.assert_called_once_with(mock.ANY) self.assertEqual([mock.call('list_hardware_info'), mock.call('wait_for_disks')], @@ -283,19 +275,17 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest): @mock.patch.object(hardware, 'dispatch_to_managers', autospec=True) @mock.patch.object(agent.IronicPythonAgent, '_wait_for_interface', autospec=True) - @mock.patch('oslo_service.wsgi.Server', autospec=True) @mock.patch.object(hardware, 'get_managers', autospec=True) - def test_run_with_ssl(self, mock_get_managers, mock_wsgi, + def test_run_with_ssl(self, mock_get_managers, mock_wait, mock_dispatch): CONF.set_override('inspection_callback_url', '') CONF.set_override('listen_tls', True) CONF.set_override('md5_enabled', False) - wsgi_server = mock_wsgi.return_value - def set_serve_api(): + def set_serve_api(*args, **kwargs): self.agent.serve_api = False - wsgi_server.start.side_effect = set_serve_api + self.agent.api.start = mock.Mock(side_effect=set_serve_api) self.agent.heartbeater = mock.Mock() self.agent.api_client.lookup_node = mock.Mock() self.agent.api_client.lookup_node.return_value = { @@ -310,11 +300,7 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest): self.agent.run() - mock_wsgi.assert_called_once_with(CONF, 'ironic-python-agent', - app=self.agent.api, - host=mock.ANY, port=9999, - use_ssl=True) - wsgi_server.start.assert_called_once_with() + self.agent.api.start.assert_called_once_with(mock.ANY, mock.ANY) mock_wait.assert_called_once_with(mock.ANY) self.assertEqual([mock.call('list_hardware_info'), mock.call('wait_for_disks')], @@ -329,15 +315,12 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest): @mock.patch.object(hardware, 'dispatch_to_managers', autospec=True) @mock.patch.object(agent.IronicPythonAgent, '_wait_for_interface', autospec=True) - @mock.patch('oslo_service.wsgi.Server', autospec=True) @mock.patch.object(hardware, 'get_managers', autospec=True) - def test_url_from_mdns_by_default(self, mock_get_managers, mock_wsgi, + def test_url_from_mdns_by_default(self, mock_get_managers, mock_wait, mock_dispatch, mock_mdns): CONF.set_override('inspection_callback_url', '') mock_mdns.return_value = 'https://example.com', {} - wsgi_server = mock_wsgi.return_value - self.agent = agent.IronicPythonAgent(None, agent.Host('203.0.113.1', 9990), agent.Host('192.0.2.1', 9999), @@ -349,10 +332,10 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest): False, None) - def set_serve_api(): + def set_serve_api(*args, **kwargs): self.agent.serve_api = False - wsgi_server.start.side_effect = set_serve_api + self.agent.api.start = mock.Mock(side_effect=set_serve_api) self.agent.heartbeater = mock.Mock() self.agent.api_client.lookup_node = mock.Mock() self.agent.api_client.lookup_node.return_value = { @@ -366,11 +349,7 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest): self.agent.run() - mock_wsgi.assert_called_once_with(CONF, 'ironic-python-agent', - app=self.agent.api, - host=mock.ANY, port=9999, - use_ssl=False) - wsgi_server.start.assert_called_once_with() + self.agent.api.start.assert_called_once_with(mock.ANY, mock.ANY) mock_wait.assert_called_once_with(mock.ANY) self.assertEqual([mock.call('list_hardware_info'), mock.call('wait_for_disks')], @@ -384,9 +363,8 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest): @mock.patch.object(hardware, 'dispatch_to_managers', autospec=True) @mock.patch.object(agent.IronicPythonAgent, '_wait_for_interface', autospec=True) - @mock.patch('oslo_service.wsgi.Server', autospec=True) @mock.patch.object(hardware, 'get_managers', autospec=True) - def test_url_from_mdns_explicitly(self, mock_get_managers, mock_wsgi, + def test_url_from_mdns_explicitly(self, mock_get_managers, mock_wait, mock_dispatch, mock_mdns): CONF.set_override('inspection_callback_url', '') CONF.set_override('disk_wait_attempts', 0) @@ -395,8 +373,6 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest): 'ipa_disk_wait_attempts': '42', } - wsgi_server = mock_wsgi.return_value - self.agent = agent.IronicPythonAgent('mdns', agent.Host('203.0.113.1', 9990), agent.Host('192.0.2.1', 9999), @@ -408,10 +384,10 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest): False, None) - def set_serve_api(): + def set_serve_api(*args, **kwargs): self.agent.serve_api = False - wsgi_server.start.side_effect = set_serve_api + self.agent.api.start = mock.Mock(side_effect=set_serve_api) self.agent.heartbeater = mock.Mock() self.agent.api_client.lookup_node = mock.Mock() self.agent.api_client.lookup_node.return_value = { @@ -425,11 +401,7 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest): self.agent.run() - mock_wsgi.assert_called_once_with(CONF, 'ironic-python-agent', - app=self.agent.api, - host=mock.ANY, port=9999, - use_ssl=False) - wsgi_server.start.assert_called_once_with() + self.agent.api.start.assert_called_once_with(mock.ANY, mock.ANY) mock_wait.assert_called_once_with(mock.ANY) self.assertEqual([mock.call('list_hardware_info'), mock.call('wait_for_disks')], @@ -444,18 +416,15 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest): @mock.patch.object(hardware, 'dispatch_to_managers', autospec=True) @mock.patch.object(agent.IronicPythonAgent, '_wait_for_interface', autospec=True) - @mock.patch('oslo_service.wsgi.Server', autospec=True) @mock.patch.object(hardware, 'get_managers', autospec=True) - def test_run_agent_token(self, mock_get_managers, mock_wsgi, + def test_run_agent_token(self, mock_get_managers, mock_wait, mock_dispatch): CONF.set_override('inspection_callback_url', '') - wsgi_server = mock_wsgi.return_value - - def set_serve_api(): + def set_serve_api(*args, **kwargs): self.agent.serve_api = False - wsgi_server.start.side_effect = set_serve_api + self.agent.api.start = mock.Mock(side_effect=set_serve_api) self.agent.heartbeater = mock.Mock() self.agent.api_client.lookup_node = mock.Mock() self.agent.api_client.lookup_node.return_value = { @@ -470,11 +439,7 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest): self.agent.run() - mock_wsgi.assert_called_once_with(CONF, 'ironic-python-agent', - app=self.agent.api, - host=mock.ANY, port=9999, - use_ssl=False) - wsgi_server.start.assert_called_once_with() + self.agent.api.start.assert_called_once_with(mock.ANY, mock.ANY) mock_wait.assert_called_once_with(mock.ANY) self.assertEqual([mock.call('list_hardware_info'), mock.call('wait_for_disks')], @@ -489,18 +454,15 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest): @mock.patch.object(hardware, 'dispatch_to_managers', autospec=True) @mock.patch.object(agent.IronicPythonAgent, '_wait_for_interface', autospec=True) - @mock.patch('oslo_service.wsgi.Server', autospec=True) @mock.patch.object(hardware, 'get_managers', autospec=True) - def test_run_listen_host_port(self, mock_get_managers, mock_wsgi, + def test_run_listen_host_port(self, mock_get_managers, mock_wait, mock_dispatch): CONF.set_override('inspection_callback_url', '') - wsgi_server = mock_wsgi.return_value - - def set_serve_api(): + def set_serve_api(*args, **kwargs): self.agent.serve_api = False - wsgi_server.start.side_effect = set_serve_api + self.agent.api.start = mock.Mock(side_effect=set_serve_api) self.agent.heartbeater = mock.Mock() self.agent.listen_address = mock.Mock() self.agent.listen_address.hostname = '2001:db8:dead:beef::cafe' @@ -517,33 +479,25 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest): self.agent.run() - mock_wsgi.assert_called_once_with(CONF, 'ironic-python-agent', - app=self.agent.api, - host='2001:db8:dead:beef::cafe', - port=9998, - use_ssl=False) - wsgi_server.start.assert_called_once_with() + self.agent.api.start.assert_called_once_with(mock.ANY, mock.ANY) mock_wait.assert_called_once_with(mock.ANY) self.assertEqual([mock.call('list_hardware_info'), mock.call('wait_for_disks')], mock_dispatch.call_args_list) self.agent.heartbeater.start.assert_called_once_with() - @mock.patch('eventlet.sleep', autospec=True) + @mock.patch('time.sleep', autospec=True) @mock.patch( 'ironic_python_agent.hardware_managers.cna._detect_cna_card', mock.Mock()) @mock.patch.object(agent.IronicPythonAgent, '_wait_for_interface', autospec=True) @mock.patch.object(hardware, 'dispatch_to_managers', autospec=True) - @mock.patch('oslo_service.wsgi.Server', autospec=True) @mock.patch.object(hardware, 'get_managers', autospec=True) - def test_run_raise_keyboard_interrupt(self, mock_get_managers, mock_wsgi, + def test_run_raise_keyboard_interrupt(self, mock_get_managers, mock_dispatch, mock_wait, mock_sleep): CONF.set_override('inspection_callback_url', '') - - wsgi_server = mock_wsgi.return_value mock_sleep.side_effect = KeyboardInterrupt() self.agent.heartbeater = mock.Mock() self.agent.api_client.lookup_node = mock.Mock() @@ -556,17 +510,15 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest): } } + self.agent.api.start = mock.Mock() + self.agent.run() self.assertTrue(mock_wait.called) self.assertEqual([mock.call('list_hardware_info'), mock.call('wait_for_disks')], mock_dispatch.call_args_list) - mock_wsgi.assert_called_once_with(CONF, 'ironic-python-agent', - app=self.agent.api, - host=mock.ANY, port=9999, - use_ssl=False) - wsgi_server.start.assert_called_once_with() + self.agent.api.start.assert_called_once_with(mock.ANY, mock.ANY) self.agent.heartbeater.start.assert_called_once_with() @mock.patch.object(hardware, '_enable_multipath', autospec=True) @@ -576,7 +528,6 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest): '_wait_for_interface', autospec=True) @mock.patch.object(inspector, 'inspect', autospec=True) @mock.patch.object(hardware, 'dispatch_to_managers', autospec=True) - @mock.patch('oslo_service.wsgi.Server', autospec=True) @mock.patch.object(hardware.HardwareManager, 'list_hardware_info', autospec=True) @mock.patch( @@ -585,16 +536,14 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest): autospec=True ) def test_run_with_inspection(self, mock_hardware, mock_list_hardware, - mock_wsgi, mock_dispatch, mock_inspector, + mock_dispatch, mock_inspector, mock_wait, mock_mpath): CONF.set_override('inspection_callback_url', 'http://foo/bar') mock_hardware.return_value = 0 - def set_serve_api(): + def set_serve_api(*args, **kwargs): self.agent.serve_api = False - - wsgi_server = mock_wsgi.return_value - wsgi_server.start.side_effect = set_serve_api + self.agent.api.start = mock.Mock(side_effect=set_serve_api) mock_inspector.return_value = 'uuid' @@ -610,11 +559,7 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest): } self.agent.run() - mock_wsgi.assert_called_once_with(CONF, 'ironic-python-agent', - app=self.agent.api, - host=mock.ANY, port=9999, - use_ssl=False) - wsgi_server.start.assert_called_once_with() + self.agent.api.start.assert_called_once_with(mock.ANY, mock.ANY) mock_inspector.assert_called_once_with() self.assertEqual(1, self.agent.api_client.lookup_node.call_count) @@ -637,7 +582,6 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest): '_wait_for_interface', autospec=True) @mock.patch.object(inspector, 'inspect', autospec=True) @mock.patch.object(hardware, 'dispatch_to_managers', autospec=True) - @mock.patch('oslo_service.wsgi.Server', autospec=True) @mock.patch.object(hardware.HardwareManager, 'list_hardware_info', autospec=True) @mock.patch( @@ -648,7 +592,6 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest): def test_run_with_inspection_without_apiurl(self, mock_hardware, mock_list_hardware, - mock_wsgi, mock_dispatch, mock_inspector, mock_wait, @@ -675,19 +618,13 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest): self.assertFalse(hasattr(self.agent, 'api_client')) self.assertFalse(hasattr(self.agent, 'heartbeater')) - def set_serve_api(): + def set_serve_api(*args, **kwargs): self.agent.serve_api = False - - wsgi_server = mock_wsgi.return_value - wsgi_server.start.side_effect = set_serve_api + self.agent.api.start = mock.Mock(side_effect=set_serve_api) self.agent.run() - mock_wsgi.assert_called_once_with(CONF, 'ironic-python-agent', - app=self.agent.api, - host=mock.ANY, port=9999, - use_ssl=False) - wsgi_server.start.assert_called_once_with() + self.agent.api.start.assert_called_once_with(mock.ANY, mock.ANY) mock_inspector.assert_called_once_with() @@ -703,7 +640,6 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest): '_wait_for_interface', autospec=True) @mock.patch.object(inspector, 'inspect', autospec=True) @mock.patch.object(hardware, 'dispatch_to_managers', autospec=True) - @mock.patch('oslo_service.wsgi.Server', autospec=True) @mock.patch.object(hardware.HardwareManager, 'list_hardware_info', autospec=True) @mock.patch( @@ -714,7 +650,6 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest): def test_run_without_inspection_and_apiurl(self, mock_hardware, mock_list_hardware, - mock_wsgi, mock_dispatch, mock_inspector, mock_wait, @@ -741,19 +676,13 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest): self.assertFalse(hasattr(self.agent, 'api_client')) self.assertFalse(hasattr(self.agent, 'heartbeater')) - def set_serve_api(): + def set_serve_api(*args, **kwargs): self.agent.serve_api = False - - wsgi_server = mock_wsgi.return_value - wsgi_server.start.side_effect = set_serve_api + self.agent.api.start = mock.Mock(side_effect=set_serve_api) self.agent.run() - mock_wsgi.assert_called_once_with(CONF, 'ironic-python-agent', - app=self.agent.api, - host=mock.ANY, port=9999, - use_ssl=False) - wsgi_server.start.assert_called_once_with() + self.agent.api.start.assert_called_once_with(mock.ANY, mock.ANY) self.assertFalse(mock_inspector.called) self.assertTrue(mock_wait.called) @@ -768,20 +697,17 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest): @mock.patch.object(hardware, 'dispatch_to_managers', autospec=True) @mock.patch.object(agent.IronicPythonAgent, '_wait_for_interface', autospec=True) - @mock.patch('oslo_service.wsgi.Server', autospec=True) @mock.patch.object(hardware, 'get_managers', autospec=True) - def test_run_then_lockdown(self, mock_get_managers, mock_wsgi, + def test_run_then_lockdown(self, mock_get_managers, mock_wait, mock_dispatch, mock_interfaces, mock_exec, mock_sleep): CONF.set_override('inspection_callback_url', '') - wsgi_server = mock_wsgi.return_value - - def set_serve_api(): + def set_serve_api(*args, **kwargs): self.agent.lockdown = True self.agent.serve_api = False - wsgi_server.start.side_effect = set_serve_api + self.agent.api.start = mock.Mock(side_effect=set_serve_api) self.agent.heartbeater = mock.Mock() self.agent.api_client.lookup_node = mock.Mock() self.agent.api_client.lookup_node.return_value = { @@ -802,11 +728,7 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest): self.assertRaises(StopTesting, self.agent.run) - mock_wsgi.assert_called_once_with(CONF, 'ironic-python-agent', - app=self.agent.api, - host=mock.ANY, port=9999, - use_ssl=False) - wsgi_server.start.assert_called_once_with() + self.agent.api.start.assert_called_once_with(mock.ANY, mock.ANY) mock_wait.assert_called_once_with(mock.ANY) self.assertEqual([mock.call('list_hardware_info'), mock.call('wait_for_disks')], @@ -846,16 +768,13 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest): @mock.patch.object(agent.IronicPythonAgent, '_wait_for_interface', autospec=True) @mock.patch.object(hardware, 'dispatch_to_managers', autospec=True) - @mock.patch('oslo_service.wsgi.Server', autospec=True) - def test_run_with_sleep(self, mock_wsgi, mock_dispatch, + def test_run_with_sleep(self, mock_dispatch, mock_wait, mock_sleep, mock_get_managers): CONF.set_override('inspection_callback_url', '') - def set_serve_api(): + def set_serve_api(*args, **kwargs): self.agent.serve_api = False - - wsgi_server = mock_wsgi.return_value - wsgi_server.start.side_effect = set_serve_api + self.agent.api.start = mock.Mock(side_effect=set_serve_api) self.agent.hardware_initialization_delay = 10 self.agent.heartbeater = mock.Mock() @@ -870,11 +789,7 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest): } self.agent.run() - mock_wsgi.assert_called_once_with(CONF, 'ironic-python-agent', - app=self.agent.api, - host=mock.ANY, port=9999, - use_ssl=False) - wsgi_server.start.assert_called_once_with() + self.agent.api.start.assert_called_once_with(mock.ANY, mock.ANY) self.agent.heartbeater.start.assert_called_once_with() mock_sleep.assert_called_once_with(10) @@ -999,18 +914,16 @@ class TestAgentStandalone(ironic_agent_base.IronicAgentTest): 'ironic_python_agent.hardware_managers.cna._detect_cna_card', mock.Mock()) @mock.patch.object(hardware, 'dispatch_to_managers', autospec=True) - @mock.patch('oslo_service.wsgi.Server', autospec=True) @mock.patch.object(hardware.HardwareManager, 'list_hardware_info', autospec=True) @mock.patch.object(hardware, 'get_managers', autospec=True) def test_run(self, mock_get_managers, mock_list_hardware, - mock_wsgi, mock_dispatch): - wsgi_server_request = mock_wsgi.return_value + mock_dispatch): - def set_serve_api(): + def set_serve_api(*args, **kwargs): self.agent.serve_api = False - wsgi_server_request.start.side_effect = set_serve_api + self.agent.api.start = mock.Mock(side_effect=set_serve_api) mock_dispatch.return_value = tls_utils.TlsCertificate( 'I am a cert', '/path/to/cert', '/path/to/key') @@ -1022,16 +935,11 @@ class TestAgentStandalone(ironic_agent_base.IronicAgentTest): self.agent.run() self.assertTrue(mock_get_managers.called) - mock_wsgi.assert_called_once_with(CONF, 'ironic-python-agent', - app=self.agent.api, - host=mock.ANY, port=9999, - use_ssl=True) - wsgi_server_request.start.assert_called_once_with() + self.agent.api.start.assert_called_once_with( + '/path/to/cert', '/path/to/key') mock_dispatch.assert_called_once_with('generate_tls_certificate', mock.ANY) - self.assertEqual('/path/to/cert', CONF.ssl.cert_file) - self.assertEqual('/path/to/key', CONF.ssl.key_file) self.assertEqual('https', self.agent.advertise_protocol) self.assertFalse(self.agent.heartbeater.called) @@ -1040,19 +948,16 @@ class TestAgentStandalone(ironic_agent_base.IronicAgentTest): @mock.patch( 'ironic_python_agent.hardware_managers.cna._detect_cna_card', mock.Mock()) - @mock.patch('oslo_service.wsgi.Server', autospec=True) @mock.patch.object(hardware.HardwareManager, 'list_hardware_info', autospec=True) @mock.patch.object(hardware, 'get_managers', autospec=True) - def test_run_no_tls(self, mock_get_managers, mock_list_hardware, - mock_wsgi): + def test_run_no_tls(self, mock_get_managers, mock_list_hardware): CONF.set_override('enable_auto_tls', False) - wsgi_server_request = mock_wsgi.return_value - def set_serve_api(): + def set_serve_api(*args, **kwargs): self.agent.serve_api = False - wsgi_server_request.start.side_effect = set_serve_api + self.agent.api.start = mock.Mock(side_effect=set_serve_api) self.agent.heartbeater = mock.Mock() self.agent.api_client = mock.Mock() @@ -1061,11 +966,7 @@ class TestAgentStandalone(ironic_agent_base.IronicAgentTest): self.agent.run() self.assertTrue(mock_get_managers.called) - mock_wsgi.assert_called_once_with(CONF, 'ironic-python-agent', - app=self.agent.api, - host=mock.ANY, port=9999, - use_ssl=False) - wsgi_server_request.start.assert_called_once_with() + self.agent.api.start.assert_called_once_with(mock.ANY, mock.ANY) self.assertEqual('http', self.agent.advertise_protocol) self.assertFalse(self.agent.heartbeater.called) @@ -1351,17 +1252,15 @@ class TestBaseAgentVMediaToken(ironic_agent_base.IronicAgentTest): @mock.patch.object(hardware, 'dispatch_to_managers', autospec=True) @mock.patch.object(agent.IronicPythonAgent, '_wait_for_interface', autospec=True) - @mock.patch('oslo_service.wsgi.Server', autospec=True) @mock.patch.object(hardware, 'get_managers', autospec=True) - def test_run_agent_token_vmedia(self, mock_get_managers, mock_wsgi, + def test_run_agent_token_vmedia(self, mock_get_managers, mock_wait, mock_dispatch): CONF.set_override('inspection_callback_url', '') - wsgi_server = mock_wsgi.return_value - def set_serve_api(): + def set_serve_api(*args, **kwargs): self.agent.serve_api = False - wsgi_server.start.side_effect = set_serve_api + self.agent.api.start = mock.Mock(side_effect=set_serve_api) self.agent.heartbeater = mock.Mock() self.agent.api_client.lookup_node = mock.Mock() self.agent.api_client.lookup_node.return_value = { @@ -1377,11 +1276,7 @@ class TestBaseAgentVMediaToken(ironic_agent_base.IronicAgentTest): self.agent.run() self.assertFalse(self.agent.lockdown) - mock_wsgi.assert_called_once_with(CONF, 'ironic-python-agent', - app=self.agent.api, - host=mock.ANY, port=9999, - use_ssl=False) - wsgi_server.start.assert_called_once_with() + self.agent.api.start.assert_called_once_with(mock.ANY, mock.ANY) mock_wait.assert_called_once_with(mock.ANY) self.assertEqual([mock.call('list_hardware_info'), mock.call('wait_for_disks')], diff --git a/releasenotes/notes/eventlet-removal-1bd8c6c2d5f8a765.yaml b/releasenotes/notes/eventlet-removal-1bd8c6c2d5f8a765.yaml new file mode 100644 index 000000000..b2d74139a --- /dev/null +++ b/releasenotes/notes/eventlet-removal-1bd8c6c2d5f8a765.yaml @@ -0,0 +1,6 @@ +fixes: + - | + Eventlet support in OpenStack is deprecated, and the oslo libraries used + by Ironic Python Agent is deprecating support for eventlet in 2026.2. + This change removes the use of eventlet directly and ensures none of our + libraries are using eventlet-based code. diff --git a/requirements.txt b/requirements.txt index a639a50d4..81e2ee546 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,9 @@ pbr>=6.0.0 # Apache-2.0 -eventlet>=0.18.2 # MIT -oslo.config>=5.2.0 # Apache-2.0 -oslo.concurrency>=3.26.0 # Apache-2.0 -oslo.log>=4.6.1 # Apache-2.0 -oslo.service>=1.24.0 # Apache-2.0 -oslo.utils>=8.0.0 # Apache-2.0 +oslo.config>=9.7.1 # Apache-2.0 +oslo.concurrency>=7.1.0 # Apache-2.0 +oslo.log>=7.1.0 # Apache-2.0 +oslo.service>=4.1.1 # Apache-2.0 +oslo.utils>=8.2.0 # Apache-2.0 Pint>=0.5 # BSD psutil>=3.2.2 # BSD pyudev>=0.18 # LGPLv2.1+ @@ -15,3 +14,4 @@ Werkzeug>=2.0.0 # BSD License cryptography>=2.3 # BSD/Apache-2.0 tooz>=2.7.2 # Apache-2.0 zeroconf>=0.24.0 # LGPL +cheroot>=10.0.1 # BSD