Merge "Restore the N535 eventlet import check"

This commit is contained in:
Zuul
2025-10-31 03:11:16 +00:00
committed by Gerrit Code Review
14 changed files with 28 additions and 204 deletions

View File

@@ -15,11 +15,9 @@
import socket
from eventlet import patcher
from neutron_lib.utils import runtime
from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import eventletutils
from oslo_utils import timeutils
from neutron.agent.linux import utils
@@ -66,37 +64,6 @@ def is_agent_down(heart_beat_time):
cfg.CONF.agent_down_time)
class _SocketWrapper:
"""Determines if socket module is patched by eventlet
and unpatches it.
If python standard library socket module is patched, it will request
an unpached version of the socket module. The sole purpose of this
class is to workaround eventlet bug
https://github.com/eventlet/eventlet/issues/764 and for the
class to be used with get_hypervisor_hostname. This class also helps
with socket mocks as it abstracts eventlet under the hood module
imports which can be tricky to target with mocks.
TODO(mtomaska): This class(workaround) can be removed once eventlet
issue is resolved.
"""
def __init__(self):
if eventletutils.is_monkey_patched(socket.__name__):
LOG.debug("Std library socket module is patched by eventlet. "
"Requesting std library socket module from eventlet.")
self._socket = patcher.original(socket.__name__)
else:
LOG.debug("Std library socket module is not patched by eventlet. "
"Using socket module as imported from std library.")
self._socket = socket
def getaddrinfo(self, host, port, family, flags):
return self._socket.getaddrinfo(host=host,
port=port,
family=family,
flags=flags)
def get_hypervisor_hostname():
"""Get hypervisor hostname
@@ -108,12 +75,11 @@ def get_hypervisor_hostname():
'.' in hypervisor_hostname):
return hypervisor_hostname
_socket_wrap = _SocketWrapper()
try:
addrinfo = _socket_wrap.getaddrinfo(host=hypervisor_hostname,
port=None,
family=socket.AF_UNSPEC,
flags=socket.AI_CANONNAME)
addrinfo = socket.getaddrinfo(host=hypervisor_hostname,
port=None,
family=socket.AF_UNSPEC,
flags=socket.AI_CANONNAME)
# getaddrinfo returns a list of 5-tuples with;
# (family, type, proto, canonname, sockaddr)
if (addrinfo and addrinfo[0][3] and

View File

@@ -1,15 +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.
from neutron.common import eventlet_utils
eventlet_utils.monkey_patch()

View File

@@ -1,54 +0,0 @@
# Copyright (c) 2015 Cloudbase Solutions.
# All Rights Reserved.
#
# 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 os
import threading
import eventlet
from eventlet.green import threading as threading_eventlet
from oslo_log import log as logging
from oslo_utils import eventletutils
LOG = logging.getLogger(__name__)
IS_MONKEY_PATCHED = False
def monkey_patch():
global IS_MONKEY_PATCHED
if not IS_MONKEY_PATCHED:
# This environment variable will be used in eventlet 0.39.0
# https://github.com/eventlet/eventlet/commit/
# b754135b045306022a537b5797f2cb2cf47ba49b
if os.getenv('EVENTLET_MONKEYPATCH', '1') == '1':
IS_MONKEY_PATCHED = True
return
eventlet.monkey_patch()
LOG.warning('This program is using eventlet and has been '
'monkey_patched')
# pylint: disable=import-outside-toplevel
from oslo_utils import importutils
p_c_e = importutils.import_module('pyroute2.config.asyncio')
p_c_e.asyncio_config()
IS_MONKEY_PATCHED = True
def get_threading_local():
if eventletutils.is_monkey_patched('thread'):
return threading_eventlet.local()
return threading.local()

View File

@@ -14,6 +14,7 @@
# under the License.
import contextlib
import threading
from keystoneauth1 import exceptions as ks_exceptions
from keystoneauth1 import loading as ks_loading
@@ -34,7 +35,6 @@ from oslo_utils import uuidutils
from sqlalchemy.orm import attributes as sql_attr
import tenacity
from neutron.common import eventlet_utils
from neutron.notifiers import batch_notifier
@@ -53,7 +53,7 @@ NOTIFIER_ENABLE_DEFAULT = True
# callbacks from the agents (DHCP, L2), trying to update the provisioning
# status of a port. In order to handle each context notifier enable flag, a
# thread local variable is used.
_notifier_store = eventlet_utils.get_threading_local()
_notifier_store = threading.local()
@registry.has_registry_receivers

View File

@@ -1,16 +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.
from neutron.common import eventlet_utils
eventlet_utils.monkey_patch()

View File

@@ -18,9 +18,10 @@ import copy
import functools
import itertools
import random
import threading
import time
from unittest import mock
import eventlet
import netaddr
from neutron_lib.api.definitions import external_net as enet_api
from neutron_lib.callbacks import exceptions
@@ -6976,11 +6977,12 @@ class DbModelMixin:
return thing
with lock():
coro = eventlet.spawn(_lock_blocked_name_update)
coro = threading.Thread(target=_lock_blocked_name_update)
coro.start()
# wait for the coroutine to get blocked on the lock before
# we proceed to update the record underneath it
while not self._blocked_on_lock:
eventlet.sleep(0)
time.sleep(0)
ctx = context.get_admin_context()
with db_api.CONTEXT_WRITER.using(ctx):
thing = ctx.session.query(model).filter_by(id=dbid).one()
@@ -6992,7 +6994,7 @@ class DbModelMixin:
# the coroutine should have encountered a stale data error because
# the status update thread would have bumped the revision number
# while it was waiting to commit
coro.wait()
coro.join()
# another attempt should work fine
thing = _lock_blocked_name_update()
self.assertEqual('a description', thing.description)
@@ -7119,21 +7121,22 @@ class DbModelMixin:
try:
with db_api.CONTEXT_WRITER.using(ctx):
thing = ctx.session.query(model).filter_by(id=dbid).one()
eventlet.sleep(0)
time.sleep(0)
ctx.session.delete(thing)
except orm.exc.StaleDataError as e:
if e and "confirm_deleted_rows" in str(e):
self.fail("Meets bug 1916889")
with lock():
coro = eventlet.spawn(_lock_blocked_object_delete)
coro = threading.Thread(target=_lock_blocked_object_delete)
coro.start()
ctx = context.get_admin_context()
with db_api.CONTEXT_WRITER.using(ctx):
thing = ctx.session.query(model).filter_by(id=dbid).one()
eventlet.sleep(0)
time.sleep(0)
ctx.session.delete(thing)
coro.wait()
coro.join()
# The DB object with its standard_attr should be deleted
with testtools.ExpectedException(orm.exc.NoResultFound):

View File

@@ -1,16 +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.
from neutron.common import eventlet_utils
eventlet_utils.monkey_patch()

View File

@@ -17,25 +17,10 @@ import sys
from oslo_config import cfg # noqa
from neutron.agent.l3 import ha
from neutron.common import config
from neutron.common import eventlet_utils
from neutron.tests.common.agents import l3_agent
eventlet_utils.monkey_patch()
# NOTE(ralonsoh): remove when eventlet is removed.
def patch_keepalived_notifications_server():
def start_keepalived_notifications_server():
pass
ha.AgentMixin._start_keepalived_notifications_server = (
start_keepalived_notifications_server)
if __name__ == "__main__":
config.register_common_config_options()
patch_keepalived_notifications_server()
sys.exit(l3_agent.main())

View File

@@ -18,13 +18,11 @@ import os
import random
import time
import eventlet
import netaddr
from neutron_lib.tests import tools
from oslo_config import cfg
from oslo_log import log as logging
from neutron._i18n import _
from neutron.agent.linux import ip_lib
from neutron.common import utils as common_utils
from neutron.conf.agent import common as config
@@ -101,7 +99,7 @@ class BaseFullStackTestCase(testlib_api.MySQLTestCaseMixin,
agent = self.client.show_agent(agent_id)['agent']
return agent.get('alive')
wait_until_true(_agent_up)
common_utils.wait_until_true(_agent_up)
def _wait_until_agent_down(self, agent_id):
def _agent_down():
@@ -119,7 +117,7 @@ class BaseFullStackTestCase(testlib_api.MySQLTestCaseMixin,
agent = self.client.show_agent(agent_id)['agent']
return not agent.get('alive')
wait_until_true(_agent_down)
common_utils.wait_until_true(_agent_down)
def _assert_ping_during_agents_restart(
self, agents, src_namespace, ips, restart_timeout=30,
@@ -142,7 +140,7 @@ class BaseFullStackTestCase(testlib_api.MySQLTestCaseMixin,
# happen only after RPC is established
agent_names = ', '.join({agent.process_fixture.process_name
for agent in agents})
wait_until_true(
common_utils.wait_until_true(
done,
timeout=count * (ping_timeout + 1),
exception=RuntimeError("Could not ping the other VM, "
@@ -199,32 +197,6 @@ class BaseFullStackTestCase(testlib_api.MySQLTestCaseMixin,
return vms
def assert_namespace_exists(self, ns_name):
wait_until_true(
common_utils.wait_until_true(
lambda: ip_lib.network_namespace_exists(ns_name,
try_is_ready=True))
def wait_until_true(predicate, timeout=60, sleep=1, exception=None):
"""Wait until callable predicate is evaluated as True
NOTE(ralonsoh): this method should be replaced with
``neutron.common.utils.wait_until_true`` once the eventlet deprecation is
finished in the fullstack framework.
:param predicate: Callable deciding whether waiting should continue.
Best practice is to instantiate predicate with functools.partial()
:param timeout: Timeout in seconds how long should function wait.
:param sleep: Polling interval for results in seconds.
:param exception: Exception instance to raise on timeout. If None is passed
(default) then WaitTimeout exception is raised.
"""
try:
with eventlet.Timeout(timeout):
while not predicate():
eventlet.sleep(sleep)
except eventlet.Timeout:
if exception is not None:
# pylint: disable=raising-bad-type
raise exception
raise common_utils.WaitTimeout(
_("Timed out after %d seconds") % timeout)

View File

@@ -77,7 +77,7 @@ class TestLoadInterfaceDriver(base.BaseTestCase):
class TestGetHypervisorHostname(base.BaseTestCase):
@mock.patch.object(utils._SocketWrapper, 'getaddrinfo')
@mock.patch.object(socket, 'getaddrinfo')
@mock.patch('socket.gethostname')
def test_get_hypervisor_hostname_gethostname_fqdn(self, hostname_mock,
addrinfo_mock):
@@ -87,7 +87,7 @@ class TestGetHypervisorHostname(base.BaseTestCase):
utils.get_hypervisor_hostname())
addrinfo_mock.assert_not_called()
@mock.patch.object(utils._SocketWrapper, 'getaddrinfo')
@mock.patch.object(socket, 'getaddrinfo')
@mock.patch('socket.gethostname')
def test_get_hypervisor_hostname_gethostname_localhost(self, hostname_mock,
addrinfo_mock):
@@ -97,7 +97,7 @@ class TestGetHypervisorHostname(base.BaseTestCase):
utils.get_hypervisor_hostname())
addrinfo_mock.assert_not_called()
@mock.patch.object(utils._SocketWrapper, 'getaddrinfo')
@mock.patch.object(socket, 'getaddrinfo')
@mock.patch('socket.gethostname')
def test_get_hypervisor_hostname_getaddrinfo(self, hostname_mock,
addrinfo_mock):
@@ -110,7 +110,7 @@ class TestGetHypervisorHostname(base.BaseTestCase):
host='host', port=None, family=socket.AF_UNSPEC,
flags=socket.AI_CANONNAME)
@mock.patch.object(utils._SocketWrapper, 'getaddrinfo')
@mock.patch.object(socket, 'getaddrinfo')
@mock.patch('socket.gethostname')
def test_get_hypervisor_hostname_getaddrinfo_no_canonname(self,
hostname_mock,
@@ -124,7 +124,7 @@ class TestGetHypervisorHostname(base.BaseTestCase):
host='host', port=None, family=socket.AF_UNSPEC,
flags=socket.AI_CANONNAME)
@mock.patch.object(utils._SocketWrapper, 'getaddrinfo')
@mock.patch.object(socket, 'getaddrinfo')
@mock.patch('socket.gethostname')
def test_get_hypervisor_hostname_getaddrinfo_localhost(self, hostname_mock,
addrinfo_mock):
@@ -138,7 +138,7 @@ class TestGetHypervisorHostname(base.BaseTestCase):
host='host', port=None, family=socket.AF_UNSPEC,
flags=socket.AI_CANONNAME)
@mock.patch.object(utils._SocketWrapper, 'getaddrinfo')
@mock.patch.object(socket, 'getaddrinfo')
@mock.patch('socket.gethostname')
def test_get_hypervisor_hostname_getaddrinfo_fail(self, hostname_mock,
addrinfo_mock):

View File

@@ -8,7 +8,6 @@ PasteDeploy>=1.5.0 # MIT
Routes>=2.3.1 # MIT
debtcollector>=1.19.0 # Apache-2.0
decorator>=4.1.0 # BSD
eventlet>=0.36.1 # MIT
pecan>=1.4.0 # BSD
httplib2>=0.22.0 # MIT
requests>=2.32.3 # Apache-2.0

View File

@@ -239,7 +239,7 @@ select = H,N
# N535: Usage of Python eventlet module not allowed
# N536: Use assertIsNone
enable-extensions = H106,H203,H204,H205,H904,N521,N524,N529,N530,N532,N534,N535,N536
ignore = H405,H701,H702,H703,N530,N535
ignore = H405,H701,H702,H703,N530
show-source = true
exclude = ./.*,build,dist,doc