Consider schema cache state on the ready check

* empty schema cache when it fails loading so the ready check can pick
  its new state
This commit is contained in:
steve-chavez
2022-01-07 09:31:30 +01:00
committed by Wolfgang Walther
parent e13d912a79
commit aa82d2e277
7 changed files with 52 additions and 17 deletions
+1 -1
View File
@@ -9,7 +9,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- #1933, #2109, Add a minimal health check endpoint - @steve-chavez - #1933, #2109, Add a minimal health check endpoint - @steve-chavez
+ For enabling this, the `admin-server-port` config must be set explictly + For enabling this, the `admin-server-port` config must be set explictly
+ The check is at the `<host>:<admin_server_port>/live` endpoint. A 200 OK status will be returned if postgrest is alive, otherwise a 503 will be returned. + A `<host>:<admin_server_port>/live` endpoint is available for checking if postgrest is running on its port/socket. 200 OK = alive, 503 = dead.
+ A `<host>:<admin_server_port>/ready` endpoint is available for checking a correct internal state(the database connection plus the schema cache). 200 OK = ready, 503 = not ready. + A `<host>:<admin_server_port>/ready` endpoint is available for checking a correct internal state(the database connection plus the schema cache). 200 OK = ready, 503 = not ready.
### Fixed ### Fixed
+7 -7
View File
@@ -21,16 +21,16 @@ import Protolude
-- | PostgREST admin application -- | PostgREST admin application
postgrestAdmin :: AppState.AppState -> AppConfig -> Wai.Application postgrestAdmin :: AppState.AppState -> AppConfig -> Wai.Application
postgrestAdmin appState appConfig req respond = do postgrestAdmin appState appConfig req respond = do
isMainAppReachable <- isRight <$> reachMainApp appConfig isMainAppReachable <- isRight <$> reachMainApp appConfig
isSchemaCacheLoaded <- isJust <$> AppState.getDbStructure appState
isConnectionUp <-
if configDbChannelEnabled appConfig
then AppState.getIsListenerOn appState
else isRight <$> SQL.use (AppState.getPool appState) (SQL.sql "SELECT 1")
case Wai.pathInfo req of case Wai.pathInfo req of
["ready"] -> ["ready"] ->
if configDbChannelEnabled appConfig then do respond $ Wai.responseLBS (if isMainAppReachable && isConnectionUp && isSchemaCacheLoaded then HTTP.status200 else HTTP.status503) [] mempty
listenerOn <- AppState.getIsListenerOn appState
respond $ Wai.responseLBS (if listenerOn && isMainAppReachable then HTTP.status200 else HTTP.status503) [] mempty
else do
result <- SQL.use (AppState.getPool appState) $ SQL.sql "SELECT 1"
respond $ Wai.responseLBS (if isRight result && isMainAppReachable 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.status503) [] mempty
_ -> _ ->
+2 -3
View File
@@ -108,9 +108,8 @@ putPgVersion = atomicWriteIORef . statePgVersion
getDbStructure :: AppState -> IO (Maybe DbStructure) getDbStructure :: AppState -> IO (Maybe DbStructure)
getDbStructure = readIORef . stateDbStructure getDbStructure = readIORef . stateDbStructure
putDbStructure :: AppState -> DbStructure -> IO () putDbStructure :: AppState -> Maybe DbStructure -> IO ()
putDbStructure appState structure = putDbStructure appState = atomicWriteIORef (stateDbStructure appState)
atomicWriteIORef (stateDbStructure appState) $ Just structure
getJsonDbS :: AppState -> IO ByteString getJsonDbS :: AppState -> IO ByteString
getJsonDbS = readIORef . stateJsonDbS getJsonDbS = readIORef . stateJsonDbS
+6 -4
View File
@@ -91,6 +91,7 @@ connectionWorker appState = do
-- do nothing and proceed if the load was successful -- do nothing and proceed if the load was successful
return () return ()
SCOnRetry -> SCOnRetry ->
-- retry reloading the schema cache
work work
SCFatalFail -> SCFatalFail ->
-- die if our schema cache query has an error -- die if our schema cache query has an error
@@ -169,12 +170,13 @@ loadSchemaCache appState = do
AppState.logWithZTime appState hint AppState.logWithZTime appState hint
return SCFatalFail return SCFatalFail
Nothing -> do Nothing -> do
AppState.putDbStructure appState Nothing
AppState.logWithZTime appState "An error ocurred when loading the schema cache" AppState.logWithZTime appState "An error ocurred when loading the schema cache"
putErr putErr
return SCOnRetry return SCOnRetry
Right dbStructure -> do Right dbStructure -> do
AppState.putDbStructure appState dbStructure AppState.putDbStructure appState (Just dbStructure)
when (isJust configDbRootSpec) . when (isJust configDbRootSpec) .
AppState.putJsonDbS appState . LBS.toStrict $ JSON.encode dbStructure AppState.putJsonDbS appState . LBS.toStrict $ JSON.encode dbStructure
AppState.logWithZTime appState "Schema cache loaded" AppState.logWithZTime appState "Schema cache loaded"
@@ -247,7 +249,7 @@ reReadConfig startingUp appState = do
AppState.logWithZTime appState hint AppState.logWithZTime appState hint
killThread (AppState.getMainThreadId appState) killThread (AppState.getMainThreadId appState)
Nothing -> do Nothing -> do
AppState.logWithZTime appState $ show e putErr
pure [] pure []
Right x -> pure x Right x -> pure x
else else
@@ -257,10 +259,10 @@ reReadConfig startingUp appState = do
if startingUp then if startingUp then
panic err -- die on invalid config if the program is starting up panic err -- die on invalid config if the program is starting up
else else
AppState.logWithZTime appState $ "Failed re-loading config: " <> err AppState.logWithZTime appState $ "Failed reloading config: " <> err
Right newConf -> do Right newConf -> do
AppState.putConfig appState newConf AppState.putConfig appState newConf
if startingUp then if startingUp then
pass pass
else else
AppState.logWithZTime appState "Config re-loaded" AppState.logWithZTime appState "Config reloaded"
+2 -2
View File
@@ -76,7 +76,7 @@ main = do
let config = cfg testDbConn let config = cfg testDbConn
appState <- AppState.initWithPool pool config appState <- AppState.initWithPool pool config
AppState.putPgVersion appState actualPgVersion AppState.putPgVersion appState actualPgVersion
AppState.putDbStructure appState baseDbStructure AppState.putDbStructure appState (Just baseDbStructure)
when (isJust $ configDbRootSpec config) $ when (isJust $ configDbRootSpec config) $
AppState.putJsonDbS appState $ toS $ JSON.encode baseDbStructure AppState.putJsonDbS appState $ toS $ JSON.encode baseDbStructure
return ((), postgrest LogCrit appState $ pure ()) return ((), postgrest LogCrit appState $ pure ())
@@ -91,7 +91,7 @@ main = do
actualPgVersion actualPgVersion
appState <- AppState.initWithPool pool config appState <- AppState.initWithPool pool config
AppState.putPgVersion appState actualPgVersion AppState.putPgVersion appState actualPgVersion
AppState.putDbStructure appState customDbStructure AppState.putDbStructure appState (Just customDbStructure)
when (isJust $ configDbRootSpec config) $ when (isJust $ configDbRootSpec config) $
AppState.putJsonDbS appState $ toS $ JSON.encode baseDbStructure AppState.putJsonDbS appState $ toS $ JSON.encode baseDbStructure
return ((), postgrest LogCrit appState $ pure ()) return ((), postgrest LogCrit appState $ pure ())
+8
View File
@@ -53,3 +53,11 @@ ALTER ROLE other_authenticator SET pgrst.db_pre_request = 'test.other_custom_hea
ALTER ROLE other_authenticator SET pgrst.db_max_rows = '100'; ALTER ROLE other_authenticator SET pgrst.db_max_rows = '100';
ALTER ROLE other_authenticator SET pgrst.db_extra_search_path = 'public, extensions, other'; ALTER ROLE other_authenticator SET pgrst.db_extra_search_path = 'public, extensions, other';
ALTER ROLE other_authenticator SET pgrst.openapi_mode = 'disabled'; ALTER ROLE other_authenticator SET pgrst.openapi_mode = 'disabled';
-- limited authenticator used for failed schema cache loads
CREATE ROLE limited_authenticator LOGIN NOINHERIT;
create or replace function no_schema_cache_for_limited_authenticator() returns void as $_$
begin
ALTER ROLE limited_authenticator SET statement_timeout to 1;
end $_$ volatile security definer language plpgsql ;
+26
View File
@@ -768,6 +768,32 @@ def test_admin_ready_wo_channel(defaultenv):
assert response.status_code == 200 assert response.status_code == 200
def test_admin_ready_includes_schema_cache_state(defaultenv):
"Should get a failed response from the admin server ready endpoint when the schema cache is not loaded"
db_uri = defaultenv["PGRST_DB_URI"].replace(
"postgrest_test_authenticator", "limited_authenticator"
)
env = {
**defaultenv,
"PGRST_DB_URI": db_uri,
"PGRST_DB_ANON_ROLE": "limited_authenticator",
}
with run(env=env, adminport=freeport()) as postgrest:
# make it impossible to load the schema cache
response = postgrest.session.post(
"/rpc/no_schema_cache_for_limited_authenticator"
)
assert response.status_code == 200
# force a reconnection so the new role setting is picked up
postgrest.process.send_signal(signal.SIGUSR1)
time.sleep(0.1)
response = postgrest.admin.get("/ready")
assert response.status_code == 503
def test_admin_not_found(defaultenv): def test_admin_not_found(defaultenv):
"Should get a not found from a undefined endpoint on the admin server" "Should get a not found from a undefined endpoint on the admin server"