From 03e71b671765138bc0bd3e9e33a0b5a668b91904 Mon Sep 17 00:00:00 2001 From: Alexander Kukushkin Date: Tue, 22 Jun 2021 08:21:29 +0200 Subject: [PATCH] The /leader endpoint returns 200 if node holds the lock (#1917) Promoting the standby cluster requires updating load-balancer health checks, which is not very convenient and easy to forget. In order to solve it, we change the behavior of the `/leader` health-check endpoint. It will return 200 without taking into account whether PostgreSQL is running as the primary or the standby_leader. --- docs/rest_api.rst | 9 +++++---- patroni/api.py | 8 ++++++-- tests/test_api.py | 6 +++++- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/docs/rest_api.rst b/docs/rest_api.rst index 74003540..2bd2147f 100644 --- a/docs/rest_api.rst +++ b/docs/rest_api.rst @@ -9,14 +9,17 @@ Health check endpoints ---------------------- For all health check ``GET`` requests Patroni returns a JSON document with the status of the node, along with the HTTP status code. If you don't want or don't need the JSON document, you might consider using the ``OPTIONS`` method instead of ``GET``. -- The following requests to Patroni REST API will return HTTP status code **200** only when the Patroni node is running as the leader: +- The following requests to Patroni REST API will return HTTP status code **200** only when the Patroni node is running as the primary with leader lock: - ``GET /`` - ``GET /master`` - - ``GET /leader`` - ``GET /primary`` - ``GET /read-write`` +- ``GET /standby-leader``: returns HTTP status code **200** only when the Patroni node is running as the leader in a :ref:`standby cluster `. + +- ``GET /leader``: returns HTTP status code **200** when the Patroni node has the leader lock. The major difference from the two previous endpoints is that it doesn't take into account whether PostgreSQL is running as the ``primary`` or the ``standby_leader``. + - ``GET /replica``: replica health check endpoint. It returns HTTP status code **200** only when the Patroni node is in the state ``running``, the role is ``replica`` and ``noloadbalance`` tag is not set. - ``GET /replica?lag=``: replica check endpoint. In addition to checks from ``replica``, it also checks replication latency and returns status code **200** only when it is below specified value. The key cluster.last_leader_operation from DCS is used for Leader wal position and compute latency on replica for performance reasons. max-lag can be specified in bytes (integer) or in human readable values, for e.g. 16kB, 64MB, 1GB. @@ -28,8 +31,6 @@ For all health check ``GET`` requests Patroni returns a JSON document with the s - ``GET /read-only``: like the above endpoint, but also includes the primary. -- ``GET /standby-leader``: returns HTTP status code **200** only when the Patroni node is running as the leader in a :ref:`standby cluster `. - - ``GET /synchronous`` or ``GET /sync``: returns HTTP status code **200** only when the Patroni node is running as a synchronous standby. - ``GET /asynchronous`` or ``GET /async``: returns HTTP status code **200** only when the Patroni node is running as an asynchronous standby. diff --git a/patroni/api.py b/patroni/api.py index 4fbc19e5..df06ec8e 100644 --- a/patroni/api.py +++ b/patroni/api.py @@ -108,9 +108,11 @@ class RestApiHandler(BaseHTTPRequestHandler): response.get('role') == 'replica' and response.get('state') == 'running' else 503 if not cluster and patroni.ha.is_paused(): + leader_status_code = 200 if response.get('role') in ('master', 'standby_leader') else 503 primary_status_code = 200 if response.get('role') == 'master' else 503 standby_leader_status_code = 200 if response.get('role') == 'standby_leader' else 503 elif patroni.ha.is_leader(): + leader_status_code = 200 if patroni.ha.is_standby_cluster(): primary_status_code = replica_status_code = 503 standby_leader_status_code = 200 if response.get('role') in ('replica', 'standby_leader') else 503 @@ -118,13 +120,15 @@ class RestApiHandler(BaseHTTPRequestHandler): primary_status_code = 200 standby_leader_status_code = 503 else: - primary_status_code = standby_leader_status_code = 503 + leader_status_code = primary_status_code = standby_leader_status_code = 503 status_code = 503 if 'standby_leader' in path or 'standby-leader' in path: status_code = standby_leader_status_code - elif 'master' in path or 'leader' in path or 'primary' in path or 'read-write' in path: + elif 'leader' in path: + status_code = leader_status_code + elif 'master' in path or 'primary' in path or 'read-write' in path: status_code = primary_status_code elif 'replica' in path: status_code = replica_status_code diff --git a/tests/test_api.py b/tests/test_api.py index 079753f4..038d1f99 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -174,7 +174,7 @@ class TestRestApiHandler(unittest.TestCase): MockRestApiServer(RestApiHandler, 'GET /replica') with patch.object(RestApiHandler, 'get_postgresql_status', Mock(return_value={'state': 'running'})): MockRestApiServer(RestApiHandler, 'GET /health') - MockRestApiServer(RestApiHandler, 'GET /master') + MockRestApiServer(RestApiHandler, 'GET /leader') MockPatroni.dcs.cluster.sync.members = [MockPostgresql.name] MockPatroni.dcs.cluster.is_synchronous_mode = Mock(return_value=True) with patch.object(RestApiHandler, 'get_postgresql_status', Mock(return_value={'role': 'replica'})): @@ -507,3 +507,7 @@ class TestRestApiServer(unittest.TestCase): Mock(return_value=(mock_request, mock_address)) ): self.srv._handle_request_noblock() + + @patch('ssl._ssl._test_decode_cert', Mock()) + def test_reload_local_certificate(self): + self.assertTrue(self.srv.reload_local_certificate())