diff --git a/lower-constraints.txt b/lower-constraints.txt index 65618eb..c53df18 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -6,7 +6,7 @@ cffi==1.7.0 debtcollector==1.2.0 docutils==0.11 dulwich==0.15.0 -eventlet==0.18.2 +eventlet==0.21.0 extras==1.0.0 fixtures==3.0.0 gitdb==0.6.4 diff --git a/oslo_privsep/daemon.py b/oslo_privsep/daemon.py index 1cf4145..3297e72 100644 --- a/oslo_privsep/daemon.py +++ b/oslo_privsep/daemon.py @@ -57,6 +57,7 @@ import tempfile import threading import eventlet +from eventlet import patcher from oslo_config import cfg from oslo_log import log as logging from oslo_utils import encodeutils @@ -75,6 +76,25 @@ if platform.system() == 'Linux': LOG = logging.getLogger(__name__) +EVENTLET_MODULES = ('os', 'select', 'socket', 'thread', 'time', 'MySQLdb', + 'builtins', 'subprocess') +EVENTLET_LIBRARIES = [] + + +def _null(): + return [] + + +for module in EVENTLET_MODULES: + if hasattr(patcher, '_green_%s_modules' % module): + method = getattr(patcher, '_green_%s_modules' % module) + elif hasattr(patcher, '_green_%s' % module): + method = getattr(patcher, '_green_%s' % module) + else: + method = _null() + EVENTLET_LIBRARIES.append((module, method)) + + @enum.unique class StdioFd(enum.IntEnum): # NOTE(gus): We can't use sys.std*.fileno() here. sys.std* @@ -258,6 +278,21 @@ def replace_logging(handler, log_root=None): log_root.addHandler(handler) +def un_monkey_patch(): + for eventlet_mod_name, func_modules in EVENTLET_LIBRARIES: + if not eventlet.patcher.is_monkey_patched(eventlet_mod_name): + continue + + for name, mod in func_modules(): + patched_mod = sys.modules.get(name) + orig_mod = eventlet.patcher.original(name) + for attr_name in mod.__patched__: + patched_attr = getattr(mod, attr_name, None) + unpatched_attr = getattr(orig_mod, attr_name, None) + if patched_attr is not None: + setattr(patched_mod, attr_name, unpatched_attr) + + class ForkingClientChannel(_ClientChannel): def __init__(self, context): """Start privsep daemon using fork() @@ -279,6 +314,7 @@ class ForkingClientChannel(_ClientChannel): if os.fork() == 0: # child + un_monkey_patch() channel = comm.ServerChannel(sock_b) sock_a.close() diff --git a/oslo_privsep/tests/test_daemon.py b/oslo_privsep/tests/test_daemon.py index b39ce61..01eb52b 100644 --- a/oslo_privsep/tests/test_daemon.py +++ b/oslo_privsep/tests/test_daemon.py @@ -13,6 +13,7 @@ # under the License. import copy +import eventlet import fixtures import functools import logging as pylogging @@ -215,3 +216,30 @@ class ClientChannelTestCase(base.BaseTestCase): with mock.patch.object(daemon.LOG, 'warning') as mock_warning: self.client_channel.out_of_band([daemon.Message.PING]) mock_warning.assert_called_once() + + +class UnMonkeyPatch(base.BaseTestCase): + + def test_un_monkey_patch(self): + self.assertFalse(any( + eventlet.patcher.is_monkey_patched(eventlet_mod_name) + for eventlet_mod_name in daemon.EVENTLET_MODULES)) + + eventlet.monkey_patch() + self.assertTrue(any( + eventlet.patcher.is_monkey_patched(eventlet_mod_name) + for eventlet_mod_name in daemon.EVENTLET_MODULES)) + + daemon.un_monkey_patch() + for eventlet_mod_name, func_modules in daemon.EVENTLET_LIBRARIES: + if not eventlet.patcher.is_monkey_patched(eventlet_mod_name): + continue + + for name, green_mod in func_modules(): + orig_mod = eventlet.patcher.original(name) + patched_mod = sys.modules.get(name) + for attr_name in green_mod.__patched__: + un_monkey_patched_attr = getattr(patched_mod, attr_name, + None) + original_attr = getattr(orig_mod, attr_name, None) + self.assertEqual(un_monkey_patched_attr, original_attr) diff --git a/releasenotes/notes/un-monkey-patch-privileged-daemon-160e00296549df3d.yaml b/releasenotes/notes/un-monkey-patch-privileged-daemon-160e00296549df3d.yaml new file mode 100644 index 0000000..3f00ef8 --- /dev/null +++ b/releasenotes/notes/un-monkey-patch-privileged-daemon-160e00296549df3d.yaml @@ -0,0 +1,11 @@ +--- +other: + - | + The ``oslo.privsep`` client can be called from a program using eventlet. + If ``eventlet.monkey_patch``, some libraries will be patched, for example + ``threading`` or ``os``. When the root daemon is forked from the client + process, those libraries remain patched. Now, when the daemon is forked + from the client process, those libraries and methods are restored to the + original values. The goal is to prevent some timeouts when using eventlet + threads (user threads); system threads are preemptive and the code does + not need to care about the executor token. diff --git a/requirements.txt b/requirements.txt index 7228b1a..8541ac3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,6 +7,6 @@ oslo.i18n>=3.15.3 # Apache-2.0 oslo.config>=5.2.0 # Apache-2.0 oslo.utils>=3.33.0 # Apache-2.0 cffi>=1.7.0 # MIT -eventlet!=0.18.3,!=0.20.1,>=0.18.2 # MIT +eventlet>=0.21.0 # MIT greenlet>=0.4.14 # MIT msgpack>=0.6.0 # Apache-2.0