feat: /live and /ready respond with 500 on failure
503 is still used by /ready to indicate a transient state that can be recovered from.
This commit is contained in:
committed by
Steve Chavez
parent
f9e9740999
commit
0060abeb01
@@ -42,6 +42,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
- #3404, Clarify the `PGRST121` (could not parse RAISE 'PGRST') error message - @laurenceisla
|
- #3404, Clarify the `PGRST121` (could not parse RAISE 'PGRST') error message - @laurenceisla
|
||||||
- #3267, Fix wrong `503 Service Unavailable` on pg error `53400` - @taimoorzaeem
|
- #3267, Fix wrong `503 Service Unavailable` on pg error `53400` - @taimoorzaeem
|
||||||
- #2985, Fix not adding `application_name` on all connection strings - @steve-chavez
|
- #2985, Fix not adding `application_name` on all connection strings - @steve-chavez
|
||||||
|
- #3424, Admin `/live` and `/ready` now differentiates a failure as 500 status - @steve-chavez
|
||||||
|
+ 503 status is still given when postgREST is in a recovering state
|
||||||
|
|
||||||
### Deprecated
|
### Deprecated
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
Admin Server
|
Admin Server
|
||||||
############
|
############
|
||||||
|
|
||||||
PostgREST provides an admin server that can be enabled by setting :ref:`admin-server-port` to the port number of your preference.
|
PostgREST provides an admin server that can be enabled by setting :ref:`admin-server-port`.
|
||||||
|
|
||||||
.. _health_check:
|
.. _health_check:
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@ Two endpoints ``live`` and ``ready`` will then be available.
|
|||||||
Live
|
Live
|
||||||
----
|
----
|
||||||
|
|
||||||
The ``live`` endpoint verifies if PostgREST is running on its configured port. A request will return ``200 OK`` if PostgREST is alive or ``503`` otherwise.
|
The ``live`` endpoint verifies if PostgREST is running on its configured port. A request will return ``200 OK`` if PostgREST is alive or ``500`` otherwise.
|
||||||
|
|
||||||
For instance, to verify if PostgREST is running while the ``admin-server-port`` is set to ``3001``:
|
For instance, to verify if PostgREST is running while the ``admin-server-port`` is set to ``3001``:
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ For instance, to verify if PostgREST is running while the ``admin-server-port``
|
|||||||
Ready
|
Ready
|
||||||
-----
|
-----
|
||||||
|
|
||||||
In addition, the ``ready`` endpoint checks the state of the :ref:`connection_pool` and the :ref:`schema_cache`. A request will return ``200 OK`` if both are good or ``503`` if not.
|
Additionally to the ``live`` check, the ``ready`` endpoint checks the state of the :ref:`connection_pool` and the :ref:`schema_cache`. A request will return ``200 OK`` if both are good or ``503`` if not.
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
@@ -48,6 +48,8 @@ In addition, the ``ready`` endpoint checks the state of the :ref:`connection_poo
|
|||||||
|
|
||||||
HTTP/1.1 200 OK
|
HTTP/1.1 200 OK
|
||||||
|
|
||||||
|
PostgREST will try to recover from the ``503`` state with :ref:`automatic_recovery`.
|
||||||
|
|
||||||
Metrics
|
Metrics
|
||||||
=======
|
=======
|
||||||
|
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ admin-server-port
|
|||||||
**In-Database** `n/a`
|
**In-Database** `n/a`
|
||||||
=============== =======================
|
=============== =======================
|
||||||
|
|
||||||
Specifies the port for the :ref:`health_check` endpoints.
|
Specifies the port for the :ref:`admin_server`.
|
||||||
|
|
||||||
.. _app.settings.*:
|
.. _app.settings.*:
|
||||||
|
|
||||||
|
|||||||
+10
-3
@@ -42,12 +42,19 @@ admin :: AppState.AppState -> Wai.Application
|
|||||||
admin appState req respond = do
|
admin appState req respond = do
|
||||||
isMainAppReachable <- isRight <$> reachMainApp (AppState.getSocketREST appState)
|
isMainAppReachable <- isRight <$> reachMainApp (AppState.getSocketREST appState)
|
||||||
isLoaded <- AppState.isLoaded appState
|
isLoaded <- AppState.isLoaded appState
|
||||||
|
isPending <- AppState.isPending appState
|
||||||
|
|
||||||
case Wai.pathInfo req of
|
case Wai.pathInfo req of
|
||||||
["ready"] ->
|
|
||||||
respond $ Wai.responseLBS (if isMainAppReachable && isLoaded then HTTP.status200 else HTTP.status503) [] mempty
|
|
||||||
["live"] ->
|
["live"] ->
|
||||||
respond $ Wai.responseLBS (if isMainAppReachable then HTTP.status200 else HTTP.status503) [] mempty
|
respond $ Wai.responseLBS (if isMainAppReachable then HTTP.status200 else HTTP.status500) [] mempty
|
||||||
|
["ready"] ->
|
||||||
|
let
|
||||||
|
status | not isMainAppReachable = HTTP.status500
|
||||||
|
| isPending = HTTP.status503
|
||||||
|
| isLoaded = HTTP.status200
|
||||||
|
| otherwise = HTTP.status500
|
||||||
|
in
|
||||||
|
respond $ Wai.responseLBS status [] mempty
|
||||||
["config"] -> do
|
["config"] -> do
|
||||||
config <- AppState.getConfig appState
|
config <- AppState.getConfig appState
|
||||||
respond $ Wai.responseLBS HTTP.status200 [] (LBS.fromStrict $ encodeUtf8 $ Config.toText config)
|
respond $ Wai.responseLBS HTTP.status200 [] (LBS.fromStrict $ encodeUtf8 $ Config.toText config)
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ module PostgREST.AppState
|
|||||||
, runListener
|
, runListener
|
||||||
, getObserver
|
, getObserver
|
||||||
, isLoaded
|
, isLoaded
|
||||||
|
, isPending
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import qualified Data.Aeson as JSON
|
import qualified Data.Aeson as JSON
|
||||||
@@ -315,6 +316,12 @@ isLoaded x = do
|
|||||||
connEstablished <- isConnEstablished x
|
connEstablished <- isConnEstablished x
|
||||||
return $ scacheStatus == SCLoaded && connEstablished
|
return $ scacheStatus == SCLoaded && connEstablished
|
||||||
|
|
||||||
|
isPending :: AppState -> IO Bool
|
||||||
|
isPending x = do
|
||||||
|
scacheStatus <- readIORef $ stateSCacheStatus x
|
||||||
|
connStatus <- readIORef $ stateConnStatus x
|
||||||
|
return $ scacheStatus == SCPending || connStatus == ConnPending
|
||||||
|
|
||||||
putSCacheStatus :: AppState -> SchemaCacheStatus -> IO ()
|
putSCacheStatus :: AppState -> SchemaCacheStatus -> IO ()
|
||||||
putSCacheStatus = atomicWriteIORef . stateSCacheStatus
|
putSCacheStatus = atomicWriteIORef . stateSCacheStatus
|
||||||
|
|
||||||
@@ -338,12 +345,15 @@ loadSchemaCache appState@AppState{stateObserver=observer} = do
|
|||||||
observer $ SchemaCacheFatalErrorObs e hint
|
observer $ SchemaCacheFatalErrorObs e hint
|
||||||
return SCFatalFail
|
return SCFatalFail
|
||||||
Nothing -> do
|
Nothing -> do
|
||||||
|
putSCacheStatus appState SCPending
|
||||||
putSchemaCache appState Nothing
|
putSchemaCache appState Nothing
|
||||||
observer $ SchemaCacheNormalErrorObs e
|
observer $ SchemaCacheNormalErrorObs e
|
||||||
putSCacheStatus appState SCPending
|
|
||||||
return SCPending
|
return SCPending
|
||||||
|
|
||||||
Right sCache -> do
|
Right sCache -> do
|
||||||
|
-- IMPORTANT: While the pending schema cache state starts from running the above querySchemaCache, only at this stage we block API requests due to the usage of an
|
||||||
|
-- IORef on putSchemaCache. This is why SCacheStatus is put at SCPending here to signal the Admin server (using isPending) that we're on a recovery state.
|
||||||
|
putSCacheStatus appState SCPending
|
||||||
putSchemaCache appState $ Just sCache
|
putSchemaCache appState $ Just sCache
|
||||||
observer $ SchemaCacheQueriedObs resultTime
|
observer $ SchemaCacheQueriedObs resultTime
|
||||||
(t, _) <- timeItT $ observer $ SchemaCacheSummaryObs $ showSummary sCache
|
(t, _) <- timeItT $ observer $ SchemaCacheSummaryObs $ showSummary sCache
|
||||||
|
|||||||
+11
-4
@@ -70,6 +70,13 @@ class PostgrestProcess:
|
|||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
def wait_until_scache_starts_loading(self, max_seconds=1):
|
||||||
|
"Wait for the admin /ready return a status of 503"
|
||||||
|
|
||||||
|
wait_until_status_code(
|
||||||
|
self.admin.baseurl + "/ready", max_seconds=max_seconds, status_code=503
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def run(
|
def run(
|
||||||
@@ -120,7 +127,7 @@ def run(
|
|||||||
process.stdin.close()
|
process.stdin.close()
|
||||||
|
|
||||||
if wait_for_readiness:
|
if wait_for_readiness:
|
||||||
wait_until_ready(adminurl + "/ready", wait_max_seconds)
|
wait_until_status_code(adminurl + "/ready", wait_max_seconds, 200)
|
||||||
|
|
||||||
if no_startup_stdout:
|
if no_startup_stdout:
|
||||||
process.stdout.read()
|
process.stdout.read()
|
||||||
@@ -178,14 +185,14 @@ def wait_until_exit(postgrest):
|
|||||||
raise PostgrestTimedOut()
|
raise PostgrestTimedOut()
|
||||||
|
|
||||||
|
|
||||||
def wait_until_ready(url, max_seconds):
|
def wait_until_status_code(url, max_seconds, status_code):
|
||||||
"Wait for the given HTTP endpoint to return a status of 200."
|
"Wait for the given HTTP endpoint to return a status code"
|
||||||
session = requests_unixsocket.Session()
|
session = requests_unixsocket.Session()
|
||||||
|
|
||||||
for _ in range(max_seconds * 10):
|
for _ in range(max_seconds * 10):
|
||||||
try:
|
try:
|
||||||
response = session.get(url, timeout=1)
|
response = session.get(url, timeout=1)
|
||||||
if response.status_code == 200:
|
if response.status_code == status_code:
|
||||||
return
|
return
|
||||||
except (requests.ConnectionError, requests.ReadTimeout):
|
except (requests.ConnectionError, requests.ReadTimeout):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from util import *
|
|||||||
from postgrest import *
|
from postgrest import *
|
||||||
|
|
||||||
|
|
||||||
def test_requests__wait_for_schema_cache_reload(defaultenv):
|
def test_requests_wait_for_schema_cache_reload(defaultenv):
|
||||||
"requests that use the schema cache (e.g. resource embedding) wait for the schema cache to reload"
|
"requests that use the schema cache (e.g. resource embedding) wait for the schema cache to reload"
|
||||||
|
|
||||||
env = {
|
env = {
|
||||||
@@ -23,13 +23,14 @@ def test_requests__wait_for_schema_cache_reload(defaultenv):
|
|||||||
response = postgrest.session.get("/rpc/notify_pgrst")
|
response = postgrest.session.get("/rpc/notify_pgrst")
|
||||||
assert response.status_code == 204
|
assert response.status_code == 204
|
||||||
|
|
||||||
time.sleep(1.5) # wait for schema cache to start reloading
|
postgrest.wait_until_scache_starts_loading()
|
||||||
|
|
||||||
response = postgrest.session.get("/tpopmassn?select=*,tpop(*)")
|
response = postgrest.session.get("/tpopmassn?select=*,tpop(*)")
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
server_timings = parse_server_timings_header(response.headers["Server-Timing"])
|
plan_dur = parse_server_timings_header(response.headers["Server-Timing"])[
|
||||||
plan_dur = server_timings["plan"]
|
"plan"
|
||||||
|
]
|
||||||
assert plan_dur > 10000.0
|
assert plan_dur > 10000.0
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -739,7 +739,7 @@ def test_admin_ready_dependent_on_main_app(defaultenv):
|
|||||||
# delete the unix socket to make the main app fail
|
# delete the unix socket to make the main app fail
|
||||||
os.remove(defaultenv["PGRST_SERVER_UNIX_SOCKET"])
|
os.remove(defaultenv["PGRST_SERVER_UNIX_SOCKET"])
|
||||||
response = postgrest.admin.get("/ready")
|
response = postgrest.admin.get("/ready")
|
||||||
assert response.status_code == 503
|
assert response.status_code == 500
|
||||||
|
|
||||||
|
|
||||||
def test_admin_live_good(defaultenv):
|
def test_admin_live_good(defaultenv):
|
||||||
@@ -757,7 +757,7 @@ def test_admin_live_dependent_on_main_app(defaultenv):
|
|||||||
# delete the unix socket to make the main app fail
|
# delete the unix socket to make the main app fail
|
||||||
os.remove(defaultenv["PGRST_SERVER_UNIX_SOCKET"])
|
os.remove(defaultenv["PGRST_SERVER_UNIX_SOCKET"])
|
||||||
response = postgrest.admin.get("/live")
|
response = postgrest.admin.get("/live")
|
||||||
assert response.status_code == 503
|
assert response.status_code == 500
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("specialhostvalue", FIXTURES["specialhostvalues"])
|
@pytest.mark.parametrize("specialhostvalue", FIXTURES["specialhostvalues"])
|
||||||
|
|||||||
Reference in New Issue
Block a user