Remove eventlet from requirements and clean up references

Complete the eventlet removal by:
- Removing eventlet and greenlet from requirements.txt
- Removing eventlet monkey patching from tests/__init__.py
- Deleting the designate/cmd/eventlet directory

This completes the migration to native Python threading across
all Designate services. All services (central, producer, worker,
sink, mdns, status) now use oslo.service with the threading backend
instead of eventlet.

Assisted-By: Claude Code 4.5 Sonnet
Change-Id: If5e67fcd738c0c2a0917b2e00459ec0a8d240cc1
Signed-off-by: Omer <oschwart@redhat.com>
This commit is contained in:
Omer
2025-10-18 01:29:01 +02:00
parent b7abdf0eaa
commit 46cb4098e0
30 changed files with 270 additions and 102 deletions
+1 -1
View File
@@ -2,7 +2,7 @@
branch = True
source = designate
omit = designate/tests/*,designate/hacking/*
concurrency = greenlet
concurrency = thread
[report]
ignore_errors = True
+4 -2
View File
@@ -15,8 +15,10 @@
# under the License.
import os
# Eventlet's GreenDNS Patching will prevent the resolution of names in
# the /etc/hosts file, causing problems for installs.
# Disable eventlet's greendns monkey patching to prevent dnspython
# compatibility issues. Without this, dnspython's zone parsing fails with
# errors like "TypeError: add(): expected an Rdata" due to conflicts between
# eventlet's patched DNS resolver and dnspython's native implementation.
os.environ['EVENTLET_NO_GREENDNS'] = 'yes'
from oslo_concurrency import lockutils # noqa
+10 -19
View File
@@ -10,32 +10,23 @@
# License for the specific language governing permissions and limitations
# under the License.
"""WSGI script for Designate API."""
import os
# NOTE(oschwart): remove once the default backend is ``BackendType.THREADING``
import oslo_service.backend as service
try:
service.init_backend(service.BackendType.THREADING)
except service.exceptions.BackendAlreadySelected:
pass
from oslo_config import cfg
from oslo_log import log as logging
import oslo_messaging as messaging
from paste import deploy
import oslo_messaging as messaging # noqa: E402
from designate.common import config
from designate.common import profiler
import designate.conf
from designate import heartbeat_emitter
from designate import policy
from designate import rpc
# Set some Oslo RPC defaults
messaging.set_transport_defaults('designate')
from oslo_config import cfg # noqa: E402
from oslo_log import log as logging # noqa: E402
from paste import deploy # noqa: E402
from designate.common import config # noqa: E402
from designate.common import profiler # noqa: E402
import designate.conf # noqa: E402
from designate import heartbeat_emitter # noqa: E402
from designate import policy # noqa: E402
from designate import rpc # noqa: E402
CONF = designate.conf.CONF
CONFIG_FILES = ['api-paste.ini', 'designate.conf']
-4
View File
@@ -21,7 +21,6 @@ import random
from random import SystemRandom
import re
import string
import time
from dns import exception as dnsexception
from dns import zone as dnszone
@@ -832,9 +831,6 @@ class Service(service.RPCService):
if zone.obj_attr_is_set('recordsets'):
for rrset in zone.recordsets:
# This allows eventlet to yield, as this looping operation
# can be very long-lived.
time.sleep(0)
self._create_recordset_in_storage(
context, zone, rrset, increment_serial=False
)
+45
View File
@@ -0,0 +1,45 @@
# Copyright 2012 Managed I.T.
#
# Author: Kiall Mac Innes <kiall@managedit.ie>
#
# 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.
from oslo_service import backend
from oslo_service.backend import exceptions as backend_exceptions
# Only initialize backend if not already set
try:
backend.init_backend(backend.BackendType.THREADING)
except backend_exceptions.BackendAlreadySelected:
# Backend already initialized, this is fine
pass
from oslo_log import log # noqa
from oslo_concurrency import lockutils # noqa
import oslo_messaging as messaging # noqa
_EXTRA_DEFAULT_LOG_LEVELS = [
'kazoo.client=WARN',
'keystone=INFO',
'oslo_service.loopingcall=WARN',
]
# Set some Oslo Log defaults
log.set_defaults(default_log_levels=log.get_default_log_levels() +
_EXTRA_DEFAULT_LOG_LEVELS)
# Set some Oslo RPC defaults
messaging.set_transport_defaults('designate')
# Set some Oslo Concurrency defaults
lockutils.set_defaults(lock_path='$state_path')
-27
View File
@@ -1,27 +0,0 @@
# Copyright 2012 Managed I.T.
#
# Author: Kiall Mac Innes <kiall@managedit.ie>
#
# 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.
# NOTE(oschwart): remove once the default backend is ``BackendType.THREADING``
import oslo_service.backend as service
try:
service.init_backend(service.BackendType.THREADING)
except service.exceptions.BackendAlreadySelected:
pass
import oslo_messaging as messaging # noqa
# Set some Oslo RPC defaults
messaging.set_transport_defaults('designate')
+1 -1
View File
@@ -39,7 +39,7 @@ SINK_OPTS = [
cfg.IntOpt('workers',
help='Number of sink worker processes to spawn'),
cfg.IntOpt('threads', default=1000,
help='Number of sink greenthreads to spawn'),
help='Number of sink threads to spawn'),
cfg.ListOpt('enabled_notification_handlers', default=[],
help='Enabled Notification Handlers'),
cfg.StrOpt('listener_pool_name',
+1 -1
View File
@@ -273,7 +273,7 @@ class DNSService:
through the same TCP connection but they will be processed
sequentially.
See https://tools.ietf.org/html/draft-ietf-dnsop-5966bis-03
Raises no exception: it's to be run in an eventlet green thread
Raises no exception: it's to be run in a thread
:param addr: Tuple of the client's (IPv4 addr, Port) or
(IPv6 addr, Port, Flow info, Scope ID)
+12 -5
View File
@@ -14,11 +14,18 @@
# License for the specific language governing permissions and limitations
# under the License.
import os
# Disable eventlet's greendns monkey patching to prevent dnspython
# compatibility issues. Without this, dnspython's zone parsing fails with
# errors like "TypeError: add(): expected an Rdata" due to conflicts between
# eventlet's patched DNS resolver and dnspython's native implementation.
os.environ['EVENTLET_NO_GREENDNS'] = 'yes'
import eventlet # noqa
eventlet.monkey_patch(os=False) # noqa
from oslo_service import backend as oslo_service_backend # noqa
try:
oslo_service_backend.init_backend(
oslo_service_backend.BackendType.THREADING)
except oslo_service_backend.exceptions.BackendAlreadySelected:
# Backend already initialized, this is fine
pass
@@ -29,6 +29,12 @@ class ApiServiceTest(designate.tests.functional.TestCase):
self.config(listen=['0.0.0.0:0'], group='service:api')
# Mock oslo_service.wsgi.Server since it's incompatible with threading
# backend
self.wsgi_server_patcher = mock.patch('oslo_service.wsgi.Server')
self.mock_wsgi_server = self.wsgi_server_patcher.start()
self.addCleanup(self.wsgi_server_patcher.stop)
self.service = service.Service()
def test_start_and_stop(self):
@@ -17,7 +17,7 @@ from oslo_upgradecheck import upgradecheck
from sqlalchemy.schema import MetaData
from sqlalchemy.schema import Table
from designate.cmd.threading import status
from designate.cmd import status
from designate.storage import sql
import designate.tests.functional
+111 -9
View File
@@ -14,21 +14,18 @@ from unittest import mock
from oslo_config import fixture as cfg_fixture
import oslotest.base
from designate.cmd import api
from designate.cmd import central
from designate.cmd import mdns
from designate.cmd import producer
from designate.cmd import sink
from designate.cmd import worker
import designate.conf
CONF = designate.conf.CONF
with mock.patch('oslo_service.backend.init_backend'):
from designate.cmd.threading import api
from designate.cmd.threading import central
from designate.cmd.threading import mdns
from designate.cmd.threading import producer
from designate.cmd.threading import sink
from designate.cmd.threading import worker
@mock.patch('designate.service.wait')
@mock.patch('designate.service.serve')
@mock.patch('designate.heartbeat_emitter.get_heartbeat_emitter')
@@ -122,3 +119,108 @@ class CmdTestCase(oslotest.base.BaseTestCase):
mock_heartbeat.assert_called()
mock_serve.assert_called_with(mock.ANY, workers=1)
mock_wait.assert_called_with()
@mock.patch('designate.api.service.Service')
def test_api_rpc_already_initialized(self, mock_service, mock_read_config,
mock_log_setup, mock_heartbeat,
mock_serve, mock_wait):
CONF.set_override('workers', 1, 'service:api')
api.main()
mock_read_config.assert_called_with('designate', mock.ANY)
mock_log_setup.assert_called_with(mock.ANY, 'designate')
mock_service.assert_called_with()
mock_heartbeat.assert_called()
mock_serve.assert_called_with(mock.ANY, workers=1)
mock_wait.assert_called_with()
@mock.patch('designate.api.service.Service')
def test_api_heartbeat_stops_on_exception(
self, mock_service, mock_read_config, mock_log_setup,
mock_heartbeat, mock_serve, mock_wait):
CONF.set_override('workers', 1, 'service:api')
mock_wait.side_effect = KeyboardInterrupt()
mock_emitter = mock.Mock()
mock_heartbeat.return_value = mock_emitter
# call api.main and make sure it gets an exception
self.assertRaises(KeyboardInterrupt, api.main)
mock_emitter.stop.assert_called_once()
@mock.patch('designate.worker.service.Service')
def test_worker_init_host_called(self, mock_service, mock_read_config,
mock_log_setup, mock_heartbeat,
mock_serve, mock_wait):
CONF.set_override('workers', 1, 'service:worker')
mock_server = mock.Mock()
mock_service.return_value = mock_server
worker.main()
mock_server.init_host.assert_called_once()
mock_heartbeat.assert_called_with(mock_server.service_name)
@mock.patch('designate.producer.service.Service')
def test_producer_init_host_called(self, mock_service, mock_read_config,
mock_log_setup, mock_heartbeat,
mock_serve, mock_wait):
CONF.set_override('workers', 1, 'service:producer')
mock_server = mock.Mock()
mock_service.return_value = mock_server
producer.main()
mock_server.init_host.assert_called_once()
mock_heartbeat.assert_called_with(mock_server.service_name)
@mock.patch('designate.central.service.Service')
def test_central_rpc_already_initialized(
self, mock_service, mock_read_config, mock_log_setup,
mock_heartbeat, mock_serve, mock_wait):
CONF.set_override('workers', 1, 'service:central')
central.main()
mock_serve.assert_called_with(mock.ANY, workers=1)
@mock.patch('designate.mdns.service.Service')
def test_mdns_rpc_already_initialized(
self, mock_service, mock_read_config, mock_log_setup,
mock_heartbeat, mock_serve, mock_wait):
CONF.set_override('workers', 1, 'service:mdns')
mdns.main()
mock_serve.assert_called_with(mock.ANY, workers=1)
@mock.patch('designate.producer.service.Service')
def test_producer_rpc_already_initialized(
self, mock_service, mock_read_config, mock_log_setup,
mock_heartbeat, mock_serve, mock_wait):
CONF.set_override('workers', 1, 'service:producer')
producer.main()
mock_serve.assert_called_with(mock.ANY, workers=1)
@mock.patch('designate.sink.service.Service')
def test_sink_rpc_already_initialized(
self, mock_service, mock_read_config, mock_log_setup,
mock_heartbeat, mock_serve, mock_wait):
CONF.set_override('workers', 1, 'service:sink')
sink.main()
mock_serve.assert_called_with(mock.ANY, workers=1)
@mock.patch('designate.worker.service.Service')
def test_worker_rpc_already_initialized(
self, mock_service, mock_read_config, mock_log_setup,
mock_heartbeat, mock_serve, mock_wait):
CONF.set_override('workers', 1, 'service:worker')
worker.main()
mock_serve.assert_called_with(mock.ANY, workers=1)
+1 -1
View File
@@ -15,7 +15,7 @@ from unittest import mock
from oslo_config import fixture as cfg_fixture
import oslotest.base
from designate.cmd.threading import manage
from designate.cmd import manage
import designate.conf
from designate.manage import base
+30
View File
@@ -0,0 +1,30 @@
# 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.
from unittest import mock
import oslotest.base
from designate.cmd import status
class StatusTestCase(oslotest.base.BaseTestCase):
@mock.patch('designate.cmd.status.upgradecheck.main')
@mock.patch('designate.cmd.status.utils.find_config')
def test_main(self, mock_find_config, mock_upgradecheck_main):
mock_find_config.return_value = ['/etc/designate/designate.conf']
mock_upgradecheck_main.return_value = 0
result = status.main()
self.assertEqual(0, result)
mock_find_config.assert_called_once_with('designate.conf')
mock_upgradecheck_main.assert_called_once()
@@ -11,7 +11,6 @@
# 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 time
from unittest import mock
from oslo_config import fixture as cfg_fixture
@@ -69,11 +68,7 @@ class HeartbeatEmitterTest(oslotest.base.BaseTestCase):
def test_emit(self):
noop_emitter = heartbeat_emitter.get_heartbeat_emitter('svc')
noop_emitter.start()
time.sleep(0.125)
noop_emitter.stop()
noop_emitter._emit_heartbeat()
self.assertIn(
"<ServiceStatus service_name:'svc' hostname:'203.0.113.1' "
+15
View File
@@ -176,6 +176,21 @@ class TestRpcService(oslotest.base.BaseTestCase):
def test_rpc_service_wait(self):
self.assertIsNone(self.service.wait())
@mock.patch.object(rpc, 'get_server')
@mock.patch.object(rpc, 'get_notifier')
def test_rpc_service_init_host_called_twice(
self, mock_rpc_get_notifier, mock_rpc_get_server):
# First call should initialize RPC
self.service.init_host()
mock_rpc_get_server.assert_called_once()
mock_rpc_get_notifier.assert_called_once()
# Second call should return early without re-initializing
self.service.init_host()
# Assert still only called once (not called again)
mock_rpc_get_server.assert_called_once()
mock_rpc_get_notifier.assert_called_once()
@mock.patch.object(policy, 'init', mock.Mock())
@mock.patch.object(rpc, 'init', mock.Mock())
@@ -1,7 +1,3 @@
# Copyright 2012 Managed I.T.
#
# Author: Kiall Mac Innes <kiall@managedit.ie>
#
# 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
@@ -13,17 +9,19 @@
# 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 os
from unittest import mock
# Eventlet's GreenDNS Patching will prevent the resolution of names in
# the /etc/hosts file, causing problems for installs.
os.environ['EVENTLET_NO_GREENDNS'] = 'yes'
import oslotest.base
import eventlet # noqa
eventlet.monkey_patch(os=False)
class WSGIApiTestCase(oslotest.base.BaseTestCase):
@mock.patch('designate.api.wsgi.init_application')
def test_wsgi_api_application(self, mock_init_app):
mock_init_app.return_value = mock.Mock()
import oslo_messaging as messaging # noqa
# Import the module to test the module-level code
import designate.wsgi.api
# Set some Oslo RPC defaults
messaging.set_transport_defaults('designate')
# Verify the application was initialized
self.assertIsNotNone(designate.wsgi.api.application)
mock_init_app.assert_called_once()
-1
View File
@@ -304,7 +304,6 @@ def bind_tcp(host, port, tcp_backlog, tcp_keepidle=None):
except Exception:
LOG.info('SO_REUSEPORT not available, ignoring.')
# This option isn't available in the OS X version of eventlet
if tcp_keepidle and hasattr(socket, 'TCP_KEEPIDLE'):
sock_tcp.setsockopt(socket.IPPROTO_TCP,
socket.TCP_KEEPIDLE,
@@ -0,0 +1,11 @@
---
upgrade:
- |
Complete removal of eventlet from Designate. All services now use
native Python threading via oslo.service's threading backend.
- |
Updated oslo.service dependency to >=4.2.0 with the [threading] extra,
which is required for the threading backend support.
- |
Default thread counts have been adjusted to reduce potential memory
issues with Python native threads compared to eventlet greenthreads.
+1 -3
View File
@@ -2,9 +2,7 @@
# date but we do not test them so no guarantee of having them all correct. If
# you find any incorrect lower bounds, let us know or propose a fix.
alembic>=1.8.0 # MIT
eventlet>=0.36.0 # MIT
Flask!=0.11,>=0.10 # BSD
greenlet>=0.4.15 # MIT
Jinja2>=2.10 # BSD License (3 clause)
jsonschema>=3.2.0 # MIT
keystoneauth1>=3.4.0 # Apache-2.0
@@ -18,7 +16,7 @@ oslo.log>=4.3.0 # Apache-2.0
oslo.reports>=1.18.0 # Apache-2.0
oslo.rootwrap>=5.15.0 # Apache-2.0
oslo.serialization>=2.25.0 # Apache-2.0
oslo.service>=1.31.0 # Apache-2.0
oslo.service[threading]>=4.2.0 # Apache-2.0
oslo.upgradecheck>=1.3.0
oslo.utils>=4.7.0 # Apache-2.0
oslo.versionedobjects>=1.31.2 # Apache-2.0
+8 -8
View File
@@ -53,14 +53,14 @@ oslo.policy.enforcer =
console_scripts =
designate-rootwrap = oslo_rootwrap.cmd:main
designate-api = designate.cmd.threading.api:main
designate-central = designate.cmd.threading.central:main
designate-manage = designate.cmd.threading.manage:main
designate-mdns = designate.cmd.threading.mdns:main
designate-sink = designate.cmd.threading.sink:main
designate-worker = designate.cmd.threading.worker:main
designate-producer = designate.cmd.threading.producer:main
designate-status = designate.cmd.threading.status:main
designate-api = designate.cmd.api:main
designate-central = designate.cmd.central:main
designate-manage = designate.cmd.manage:main
designate-mdns = designate.cmd.mdns:main
designate-sink = designate.cmd.sink:main
designate-worker = designate.cmd.worker:main
designate-producer = designate.cmd.producer:main
designate-status = designate.cmd.status:main
designate.api.admin.extensions =
reports = designate.api.admin.controllers.extensions.reports:ReportsController