Add workaround for eventlet.greendns bug

Issue[1] workaround: A wrapper class which determines if socket module
was eventlet patched and request std lib socket module instead.
Also adding LOG.warning into the exception block so we dont miss
issues like this in the future.

Closes-Bug: #1980967
Related-Bug: #1926693

[1]https://github.com/eventlet/eventlet/issues/764

Change-Id: I41c4cbc1aaea95f7808e6c6dca47ecd0402351c9
(cherry picked from commit ea22307284)
This commit is contained in:
Miro Tomaska 2022-07-08 09:56:23 -05:00
parent 81809eac06
commit 363f0a972e
2 changed files with 50 additions and 16 deletions

View File

@ -16,9 +16,11 @@
import os
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.conf.agent import common as config
@ -70,6 +72,37 @@ 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
@ -81,18 +114,21 @@ def get_hypervisor_hostname():
'.' in hypervisor_hostname):
return hypervisor_hostname
_socket_wrap = _SocketWrapper()
try:
addrinfo = socket.getaddrinfo(host=hypervisor_hostname,
port=None,
family=socket.AF_UNSPEC,
flags=socket.AI_CANONNAME)
addrinfo = _socket_wrap.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
not addrinfo[0][3].startswith('localhost')):
return addrinfo[0][3]
except OSError:
pass
except OSError as os_err:
LOG.warning("Error: %s, occured while querying for fqdn. "
"get_hypervisor_hostname will just "
"return %s", os_err, hypervisor_hostname)
return hypervisor_hostname
@ -102,15 +138,13 @@ def default_rp_hypervisors(hypervisors, device_mappings,
default_hypervisor=None):
"""Fill config option 'resource_provider_hypervisors' with defaults.
Default hypervisor names to socket.gethostname() unless default_hypervisor
is set.
:param hypervisors: Config option 'resource_provider_hypervisors'
as parsed by oslo.config, that is a dict with keys of physical devices
and values of hypervisor names.
:param device_mappings: Device mappings standardized to the list-valued
format.
:param default_hypervisor: Default hypervisor hostname.
:param default_hypervisor: Default hypervisor hostname. If not set,
it tries to default to fully qualified domain name (fqdn)
"""
_default_hypervisor = default_hypervisor or get_hypervisor_hostname()

View File

@ -80,7 +80,7 @@ class TestLoadInterfaceDriver(base.BaseTestCase):
class TestGetHypervisorHostname(base.BaseTestCase):
@mock.patch('socket.getaddrinfo')
@mock.patch.object(utils._SocketWrapper, 'getaddrinfo')
@mock.patch('socket.gethostname')
def test_get_hypervisor_hostname_gethostname_fqdn(self, hostname_mock,
addrinfo_mock):
@ -90,7 +90,7 @@ class TestGetHypervisorHostname(base.BaseTestCase):
utils.get_hypervisor_hostname())
addrinfo_mock.assert_not_called()
@mock.patch('socket.getaddrinfo')
@mock.patch.object(utils._SocketWrapper, 'getaddrinfo')
@mock.patch('socket.gethostname')
def test_get_hypervisor_hostname_gethostname_localhost(self, hostname_mock,
addrinfo_mock):
@ -100,7 +100,7 @@ class TestGetHypervisorHostname(base.BaseTestCase):
utils.get_hypervisor_hostname())
addrinfo_mock.assert_not_called()
@mock.patch('socket.getaddrinfo')
@mock.patch.object(utils._SocketWrapper, 'getaddrinfo')
@mock.patch('socket.gethostname')
def test_get_hypervisor_hostname_getaddrinfo(self, hostname_mock,
addrinfo_mock):
@ -113,7 +113,7 @@ class TestGetHypervisorHostname(base.BaseTestCase):
host='host', port=None, family=socket.AF_UNSPEC,
flags=socket.AI_CANONNAME)
@mock.patch('socket.getaddrinfo')
@mock.patch.object(utils._SocketWrapper, 'getaddrinfo')
@mock.patch('socket.gethostname')
def test_get_hypervisor_hostname_getaddrinfo_no_canonname(self,
hostname_mock,
@ -127,7 +127,7 @@ class TestGetHypervisorHostname(base.BaseTestCase):
host='host', port=None, family=socket.AF_UNSPEC,
flags=socket.AI_CANONNAME)
@mock.patch('socket.getaddrinfo')
@mock.patch.object(utils._SocketWrapper, 'getaddrinfo')
@mock.patch('socket.gethostname')
def test_get_hypervisor_hostname_getaddrinfo_localhost(self, hostname_mock,
addrinfo_mock):
@ -141,7 +141,7 @@ class TestGetHypervisorHostname(base.BaseTestCase):
host='host', port=None, family=socket.AF_UNSPEC,
flags=socket.AI_CANONNAME)
@mock.patch('socket.getaddrinfo')
@mock.patch.object(utils._SocketWrapper, 'getaddrinfo')
@mock.patch('socket.gethostname')
def test_get_hypervisor_hostname_getaddrinfo_fail(self, hostname_mock,
addrinfo_mock):