Do a real http request when performing name uniqueness check (#2942)

When running in containers it is possible that the traffic is routed using `docker-proxy`, which listens on the port and accepting incoming connections.

This commit effectively sticks to the original solution from #2878
This commit is contained in:
Alexander Kukushkin
2023-11-08 14:08:02 +01:00
committed by GitHub
parent 552e8643d9
commit 3ffd598a1c
3 changed files with 32 additions and 21 deletions
+7 -9
View File
@@ -107,8 +107,6 @@ class Patroni(AbstractPatroniDaemon, Tags):
def ensure_unique_name(self) -> None:
"""A helper method to prevent splitbrain from operator naming error."""
from urllib.parse import urlparse
from urllib3.connection import HTTPConnection
from patroni.dcs import Member
cluster = self.dcs.get_cluster()
@@ -118,14 +116,14 @@ class Patroni(AbstractPatroniDaemon, Tags):
if not isinstance(member, Member):
return
try:
parts = urlparse(member.api_url)
if isinstance(parts.hostname, str):
connection = HTTPConnection(parts.hostname, port=parts.port or 80, timeout=3)
connection.connect()
logger.fatal("Can't start; there is already a node named '%s' running", self.config['name'])
sys.exit(1)
# Silence annoying WARNING: Retrying (...) messages when Patroni is quickly restarted.
# At this moment we don't have custom log levels configured and hence shouldn't lose anything useful.
self.logger.update_loggers({'urllib3.connectionpool': 'ERROR'})
_ = self.request(member, endpoint="/liveness", timeout=3)
logger.fatal("Can't start; there is already a node named '%s' running", self.config['name'])
sys.exit(1)
except Exception:
return
self.logger.update_loggers({})
def _get_tags(self) -> Dict[str, Any]:
"""Get tags configured for this node, if any.
+21 -8
View File
@@ -202,24 +202,37 @@ class PatroniLogger(Thread):
self._proxy_handler = ProxyHandler(self)
self._root_logger.addHandler(self._proxy_handler)
def update_loggers(self) -> None:
"""Configure loggers' log level as defined in ``log.loggers`` section of Patroni configuration.
def update_loggers(self, config: Dict[str, Any]) -> None:
"""Configure custom loggers' log levels.
.. note::
It creates logger objects that are not defined yet in the log manager.
:param config: :class:`dict` object with custom loggers configuration, is set either from:
* ``log.loggers`` section of Patroni configuration; or
* from the method that is trying to make sure that the node name
isn't duplicated (to silence annoying ``urllib3`` WARNING's).
:Example:
.. code-block:: python
update_loggers({'urllib3.connectionpool': 'WARNING'})
"""
loggers = deepcopy((self._config or {}).get('loggers') or {})
loggers = deepcopy(config)
for name, logger in self._root_logger.manager.loggerDict.items():
# ``Placeholder`` is a node in the log manager for which no logger has been defined. We are interested only
# in the ones that were defined
if not isinstance(logger, logging.PlaceHolder):
# if this logger is present in ``log.loggers`` Patroni configuration, use the configured level,
# otherwise use ``logging.NOTSET``, which means it will inherit the level from any parent node up to
# the root for which log level is defined.
# if this logger is present in *config*, use the configured level, otherwise
# use ``logging.NOTSET``, which means it will inherit the level
# from any parent node up to the root for which log level is defined.
level = loggers.pop(name, logging.NOTSET)
logger.setLevel(level)
# define loggers that do not exist yet and set level as configured in ``log.loggers`` section of configuration.
# define loggers that do not exist yet and set level as configured in the *config*
for name, level in loggers.items():
logger = self._root_logger.manager.getLogger(name)
logger.setLevel(level)
@@ -274,7 +287,7 @@ class PatroniLogger(Thread):
self.log_handler = new_handler
self._config = config.copy()
self.update_loggers()
self.update_loggers(config.get('loggers') or {})
def _close_old_handlers(self) -> None:
"""Close old log handlers.
+4 -4
View File
@@ -45,7 +45,7 @@ class MockFrozenImporter(object):
@patch('time.sleep', Mock())
@patch('subprocess.call', Mock(return_value=0))
@patch('patroni.psycopg.connect', psycopg_connect)
@patch('urllib3.connection.HTTPConnection.connect', Mock(side_effect=Exception))
@patch('urllib3.PoolManager.request', Mock(side_effect=Exception))
@patch.object(ConfigHandler, 'append_pg_hba', Mock())
@patch.object(ConfigHandler, 'write_postgresql_conf', Mock())
@patch.object(ConfigHandler, 'write_recovery_conf', Mock())
@@ -69,7 +69,7 @@ class TestPatroni(unittest.TestCase):
self.assertRaises(SystemExit, _main)
@patch('pkgutil.iter_importers', Mock(return_value=[MockFrozenImporter()]))
@patch('urllib3.connection.HTTPConnection.connect', Mock(side_effect=Exception))
@patch('urllib3.PoolManager.request', Mock(side_effect=Exception))
@patch('sys.frozen', Mock(return_value=True), create=True)
@patch.object(HTTPServer, '__init__', Mock())
@patch.object(etcd.Client, 'read', etcd_read)
@@ -273,8 +273,8 @@ class TestPatroni(unittest.TestCase):
)
with patch('patroni.dcs.AbstractDCS.get_cluster', Mock(return_value=bad_cluster)):
# If the api of the running node cannot be reached, this implies unique name
with patch('urllib3.connection.HTTPConnection.connect', Mock(side_effect=ConnectionError)):
with patch('urllib3.PoolManager.request', Mock(side_effect=ConnectionError)):
self.assertIsNone(self.p.ensure_unique_name())
# Only if the api of the running node is reachable do we throw an error
with patch('urllib3.connection.HTTPConnection.connect', Mock()):
with patch('urllib3.PoolManager.request', Mock()):
self.assertRaises(SystemExit, self.p.ensure_unique_name)