diff --git a/CHANGELOG.md b/CHANGELOG.md index 997775f331..4025644891 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,12 +11,14 @@ This project adheres to [Semantic Versioning](http://semver.org/). - #3558, Add the `admin-server-host` config to set the host for the admin server - @develop7 - #3607, Log to stderr when the JWT secret is less than 32 characters long - @laurenceisla - #2858, Performance improvements when calling RPCs via GET using indexes in more cases - @wolfgangwalther + - #3560, Log resolved host in "Listening on ..." messages - @develop7 ### Fixed - #3091, Broken link in OpenAPI description `externalDocs` - @salim-b - #3659, Embed One-to-One relationship with different column order properly - @wolfgangwalther - #3504, Remove `format` from `rowFilter` parameters in OpenAPI - @dantheman2865 + - #3660, Fix regression that loaded the schema cache before the in-database configuration - @steve-chavez, @laurenceisla ### Changed diff --git a/postgrest.cabal b/postgrest.cabal index a03317ec82..ce6a6383bd 100644 --- a/postgrest.cabal +++ b/postgrest.cabal @@ -66,6 +66,7 @@ library PostgREST.Logger PostgREST.MediaType PostgREST.Metrics + PostgREST.Network PostgREST.Observation PostgREST.Query PostgREST.Query.QueryBuilder @@ -115,6 +116,7 @@ library , heredoc >= 0.2 && < 0.3 , http-types >= 0.12.2 && < 0.13 , insert-ordered-containers >= 0.2.2 && < 0.3 + , iproute >= 1.7.0 && < 1.8 , jose-jwt >= 0.9.6 && < 0.11 , lens >= 4.14 && < 5.3 , lens-aeson >= 1.0.1 && < 1.3 diff --git a/src/PostgREST/Admin.hs b/src/PostgREST/Admin.hs index 325e95c065..e1b0f0b159 100644 --- a/src/PostgREST/Admin.hs +++ b/src/PostgREST/Admin.hs @@ -19,6 +19,7 @@ import Network.Socket.ByteString import PostgREST.AppState (AppState) import PostgREST.Config (AppConfig (..)) import PostgREST.Metrics (metricsToText) +import PostgREST.Network (resolveHost) import PostgREST.Observation (Observation (..)) import qualified PostgREST.AppState as AppState @@ -31,7 +32,8 @@ runAdmin :: AppState -> Warp.Settings -> IO () runAdmin appState settings = do AppConfig{configAdminServerPort} <- AppState.getConfig appState whenJust (AppState.getSocketAdmin appState) $ \adminSocket -> do - observer $ AdminStartObs configAdminServerPort + host <- resolveHost adminSocket + observer $ AdminStartObs host configAdminServerPort void . forkIO $ Warp.runSettingsSocket settings adminSocket adminApp where adminApp = admin appState diff --git a/src/PostgREST/App.hs b/src/PostgREST/App.hs index 8026ff66b5..f99d8683f2 100644 --- a/src/PostgREST/App.hs +++ b/src/PostgREST/App.hs @@ -46,6 +46,7 @@ import PostgREST.Auth (AuthResult (..)) import PostgREST.Config (AppConfig (..), LogLevel (..)) import PostgREST.Config.PgVersion (PgVersion (..)) import PostgREST.Error (Error) +import PostgREST.Network (resolveHost) import PostgREST.Observation (Observation (..)) import PostgREST.Response.Performance (ServerTiming (..), serverTimingHeader) @@ -82,7 +83,8 @@ run appState = do observer $ AppServerUnixObs path Nothing -> do port <- NS.socketPort $ AppState.getSocketREST appState - observer $ AppServerPortObs port + host <- resolveHost $ AppState.getSocketREST appState + observer $ AppServerPortObs (fromJust host) port Warp.runSettingsSocket (serverSettings conf) (AppState.getSocketREST appState) app diff --git a/src/PostgREST/AppState.hs b/src/PostgREST/AppState.hs index fe8d8b7ee5..45dd2669ab 100644 --- a/src/PostgREST/AppState.hs +++ b/src/PostgREST/AppState.hs @@ -161,7 +161,7 @@ initWithPool (sock, adminSock) pool conf loggerState metricsState observer = do deb <- let decisecond = 100000 in mkDebounce defaultDebounceSettings - { debounceAction = internalSchemaCacheLoad appState + { debounceAction = retryingSchemaCacheLoad appState , debounceFreq = decisecond , debounceEdge = leadingEdge -- runs the worker at the start and the end } @@ -355,13 +355,6 @@ putSCacheStatus = atomicWriteIORef . stateSCacheStatus getObserver :: AppState -> ObservationHandler getObserver = stateObserver -internalSchemaCacheLoad :: AppState -> IO () -internalSchemaCacheLoad appState = do - AppConfig{..} <- getConfig appState - void $ retryingSchemaCacheLoad appState - -- We cannot retry reading the in-db config after it fails immediately, because it could have user errors. We just report the error and continue. - when configDbConfig $ readInDbConfig False appState - -- | Try to load the schema cache and retry if it fails. -- -- This is done by repeatedly: 1) flushing the pool, 2) querying the version and validating that the postgres version is supported by us, and 3) loading the schema cache. @@ -369,16 +362,17 @@ internalSchemaCacheLoad appState = do -- -- + Because connections cache the pg catalog(see #2620) -- + For rapid recovery. Otherwise, the pool idle or lifetime timeout would have to be reached for new healthy connections to be acquired. -retryingSchemaCacheLoad :: AppState -> IO (Maybe PgVersion, Maybe SchemaCache) +retryingSchemaCacheLoad :: AppState -> IO () retryingSchemaCacheLoad appState@AppState{stateObserver=observer, stateMainThreadId=mainThreadId} = - retrying retryPolicy shouldRetry (\RetryStatus{rsIterNumber, rsPreviousDelay} -> do + void $ retrying retryPolicy shouldRetry (\RetryStatus{rsIterNumber, rsPreviousDelay} -> do when (rsIterNumber > 0) $ do let delay = fromMaybe 0 rsPreviousDelay `div` oneSecondInUs observer $ ConnectionRetryObs delay putNextListenerDelay appState delay flushPool appState - (,) <$> qPgVersion <*> qSchemaCache + + (,) <$> qPgVersion <*> (qInDbConfig *> qSchemaCache) ) where qPgVersion :: IO (Maybe PgVersion) @@ -400,6 +394,11 @@ retryingSchemaCacheLoad appState@AppState{stateObserver=observer, stateMainThrea putPgVersion appState actualPgVersion return $ Just actualPgVersion + qInDbConfig :: IO () + qInDbConfig = do + AppConfig{..} <- getConfig appState + when configDbConfig $ readInDbConfig False appState + qSchemaCache :: IO (Maybe SchemaCache) qSchemaCache = do conf@AppConfig{..} <- getConfig appState @@ -438,6 +437,7 @@ retryingSchemaCacheLoad appState@AppState{stateObserver=observer, stateMainThrea oneSecondInUs = 1000000 -- one second in microseconds -- | Reads the in-db config and reads the config file again +-- | We don't retry reading the in-db config after it fails immediately, because it could have user errors. We just report the error and continue. readInDbConfig :: Bool -> AppState -> IO () readInDbConfig startingUp appState@AppState{stateObserver=observer} = do AppConfig{..} <- getConfig appState diff --git a/src/PostgREST/Network.hs b/src/PostgREST/Network.hs new file mode 100644 index 0000000000..f66afa587a --- /dev/null +++ b/src/PostgREST/Network.hs @@ -0,0 +1,17 @@ +module PostgREST.Network + ( resolveHost + ) where + +import Data.IP (fromHostAddress, fromHostAddress6) +import Data.String (IsString (..)) +import qualified Network.Socket as NS + +import Protolude + +resolveHost :: NS.Socket -> IO (Maybe Text) +resolveHost sock = do + sn <- NS.getSocketName sock + case sn of + NS.SockAddrInet _ hostAddr -> pure $ Just $ fromString $ show $ fromHostAddress hostAddr + NS.SockAddrInet6 _ _ hostAddr6 _ -> pure $ Just $ fromString $ show $ fromHostAddress6 hostAddr6 + _ -> pure Nothing diff --git a/src/PostgREST/Observation.hs b/src/PostgREST/Observation.hs index 884d5f3097..fc3cbcaa94 100644 --- a/src/PostgREST/Observation.hs +++ b/src/PostgREST/Observation.hs @@ -25,9 +25,9 @@ import Protolude import Protolude.Partial (fromJust) data Observation - = AdminStartObs (Maybe Int) + = AdminStartObs (Maybe Text) (Maybe Int) | AppStartObs ByteString - | AppServerPortObs NS.PortNumber + | AppServerPortObs Text NS.PortNumber | AppServerUnixObs FilePath | ExitUnsupportedPgVersion PgVersion PgVersion | ExitDBNoRecoveryObs @@ -60,12 +60,12 @@ type ObservationHandler = Observation -> IO () observationMessage :: Observation -> Text observationMessage = \case - AdminStartObs port -> - "Admin server listening on port " <> show (fromIntegral (fromJust port) :: Integer) + AdminStartObs host port -> + "Admin server listening on " <> fromJust host <> ":" <> show (fromIntegral (fromJust port) :: Integer) AppStartObs ver -> "Starting PostgREST " <> T.decodeUtf8 ver <> "..." - AppServerPortObs port -> - "Listening on port " <> show port + AppServerPortObs host port -> + "Listening on " <> host <> ":" <> show port AppServerUnixObs sock -> "Listening on unix socket " <> show sock DBConnectedObs ver -> diff --git a/test/io/__snapshots__/test_cli/test_schema_cache_snapshot[dbRoutines].yaml b/test/io/__snapshots__/test_cli/test_schema_cache_snapshot[dbRoutines].yaml index 8c46a3393e..89262c2e8c 100644 --- a/test/io/__snapshots__/test_cli/test_schema_cache_snapshot[dbRoutines].yaml +++ b/test/io/__snapshots__/test_cli/test_schema_cache_snapshot[dbRoutines].yaml @@ -218,6 +218,40 @@ pdSchema: public pdVolatility: Volatile +- - qiName: reset_db_schemas_config + qiSchema: public + - - pdDescription: null + pdFuncSettings: [] + pdHasVariadic: false + pdName: reset_db_schemas_config + pdParams: [] + pdReturnType: + contents: + contents: + qiName: void + qiSchema: pg_catalog + tag: Scalar + tag: Single + pdSchema: public + pdVolatility: Volatile + +- - qiName: change_db_schemas_config + qiSchema: public + - - pdDescription: null + pdFuncSettings: [] + pdHasVariadic: false + pdName: change_db_schemas_config + pdParams: [] + pdReturnType: + contents: + contents: + qiName: void + qiSchema: pg_catalog + tag: Scalar + tag: Single + pdSchema: public + pdVolatility: Volatile + - - qiName: change_db_schema_and_full_reload qiSchema: public - - pdDescription: null diff --git a/test/io/fixtures.sql b/test/io/fixtures.sql index 53446adee7..7c8658cd3e 100644 --- a/test/io/fixtures.sql +++ b/test/io/fixtures.sql @@ -33,6 +33,9 @@ GRANT CREATE SCHEMA v1; GRANT USAGE ON SCHEMA v1 TO postgrest_test_anonymous; +CREATE SCHEMA test; +GRANT USAGE ON SCHEMA test TO postgrest_test_anonymous; + CREATE TABLE authors_only (); GRANT SELECT ON authors_only TO postgrest_test_author; @@ -226,3 +229,17 @@ $$ language sql; create function get_statement_timeout(items) returns text as $$ select current_setting('statement_timeout', true) as statement_timeout $$ language sql; + +create function change_db_schemas_config() returns void as $_$ +begin + alter role postgrest_test_authenticator set pgrst.db_schemas = 'test'; +end $_$ volatile security definer language plpgsql; + +create function reset_db_schemas_config() returns void as $_$ +begin + alter role postgrest_test_authenticator reset pgrst.db_schemas; +end $_$ volatile security definer language plpgsql ; + +create function test.get_current_schema() returns text as $$ + select current_schema()::text; +$$ language sql; diff --git a/test/io/test_io.py b/test/io/test_io.py index bf2e1e1569..e31f99a9d1 100644 --- a/test/io/test_io.py +++ b/test/io/test_io.py @@ -1258,6 +1258,19 @@ def test_log_postgrest_version(defaultenv): assert "Starting PostgREST %s..." % version in output[0] +def test_log_postgrest_host_and_port(defaultenv): + "PostgREST should output the host and port it is bound to." + host = "127.0.0.1" + port = freeport() + + with run( + env=defaultenv, host=host, port=port, no_startup_stdout=False + ) as postgrest: + output = postgrest.read_stdout(nlines=10) + + assert f"Listening on {host}:{port}" in output[2] # output-sensitive + + def test_succeed_w_role_having_superuser_settings(defaultenv): "Should succeed when having superuser settings on the impersonated role" @@ -1618,3 +1631,20 @@ def test_admin_metrics(defaultenv): assert "pgrst_db_pool_waiting" in response.text assert "pgrst_db_pool_available" in response.text assert "pgrst_db_pool_timeouts_total" in response.text + + +def test_schema_cache_startup_load_with_in_db_config(defaultenv, metapostgrest): + "verify that the Schema Cache loads correctly at startup, using the in-db `pgrst.db_schemas` config" + + response = metapostgrest.session.post("/rpc/change_db_schemas_config") + assert response.text == "" + assert response.status_code == 204 + + with run(env=defaultenv) as postgrest: + response = postgrest.session.get("/rpc/get_current_schema") + assert response.text == '"test"' + assert response.status_code == 200 + + response = metapostgrest.session.post("/rpc/reset_db_schemas_config") + assert response.text == "" + assert response.status_code == 204