diff --git a/patroni/__main__.py b/patroni/__main__.py index 02ba56da..229ccfb9 100644 --- a/patroni/__main__.py +++ b/patroni/__main__.py @@ -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. diff --git a/patroni/log.py b/patroni/log.py index 09d73883..6ac67a17 100644 --- a/patroni/log.py +++ b/patroni/log.py @@ -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. diff --git a/tests/test_patroni.py b/tests/test_patroni.py index 19497ab5..bf9e2871 100644 --- a/tests/test_patroni.py +++ b/tests/test_patroni.py @@ -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)