test(io): move reloading related tests to test_reloading.py
Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
This commit is contained in:
+1
-112
@@ -1,15 +1,12 @@
|
|||||||
"Auth related IO tests for PostgREST"
|
"Auth related IO tests for PostgREST"
|
||||||
|
|
||||||
from operator import attrgetter
|
from operator import attrgetter
|
||||||
import signal
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from config import BASEDIR, CONFIGSDIR, FIXTURES, SECRET
|
from config import BASEDIR, FIXTURES, SECRET
|
||||||
from util import authheader, jwtauthheader
|
from util import authheader, jwtauthheader
|
||||||
from postgrest import (
|
from postgrest import (
|
||||||
run,
|
run,
|
||||||
sleep_until_postgrest_config_reload,
|
|
||||||
sleep_until_postgrest_scache_reload,
|
|
||||||
wait_until_exit,
|
wait_until_exit,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -111,111 +108,3 @@ def test_jwt_aud_in_role_claim_key(jwtaudroleclaim, defaultenv):
|
|||||||
with run(env=env) as postgrest:
|
with run(env=env) as postgrest:
|
||||||
response = postgrest.session.get("/authors_only", headers=headers)
|
response = postgrest.session.get("/authors_only", headers=headers)
|
||||||
assert response.status_code == jwtaudroleclaim["expected_status"]
|
assert response.status_code == jwtaudroleclaim["expected_status"]
|
||||||
|
|
||||||
|
|
||||||
def test_jwt_secret_reload(tmp_path, defaultenv):
|
|
||||||
"JWT secret should be reloaded from file when PostgREST is sent SIGUSR2."
|
|
||||||
config = (CONFIGSDIR / "sigusr2-settings.config").read_text()
|
|
||||||
configfile = tmp_path / "test.config"
|
|
||||||
configfile.write_text(config)
|
|
||||||
|
|
||||||
headers = jwtauthheader({"role": "postgrest_test_author"}, SECRET)
|
|
||||||
|
|
||||||
with run(configfile, env=defaultenv) as postgrest:
|
|
||||||
response = postgrest.session.get("/authors_only", headers=headers)
|
|
||||||
assert response.status_code == 401
|
|
||||||
|
|
||||||
# change setting
|
|
||||||
configfile.write_text(config.replace("invalid" * 5, SECRET))
|
|
||||||
|
|
||||||
# reload config
|
|
||||||
postgrest.process.send_signal(signal.SIGUSR2)
|
|
||||||
|
|
||||||
sleep_until_postgrest_config_reload()
|
|
||||||
|
|
||||||
response = postgrest.session.get("/authors_only", headers=headers)
|
|
||||||
assert response.status_code == 200
|
|
||||||
|
|
||||||
|
|
||||||
def test_jwt_secret_external_file_reload(tmp_path, defaultenv):
|
|
||||||
"JWT secret external file should be reloaded when PostgREST is sent a SIGUSR2 or a NOTIFY."
|
|
||||||
headers = jwtauthheader({"role": "postgrest_test_author"}, SECRET)
|
|
||||||
|
|
||||||
external_secret_file = tmp_path / "jwt-secret-config"
|
|
||||||
external_secret_file.write_text("invalid" * 5)
|
|
||||||
|
|
||||||
env = {
|
|
||||||
**defaultenv,
|
|
||||||
"PGRST_JWT_SECRET": f"@{external_secret_file}",
|
|
||||||
"PGRST_DB_CHANNEL_ENABLED": "true",
|
|
||||||
"PGRST_DB_CONFIG": "false",
|
|
||||||
"PGRST_DB_ANON_ROLE": "postgrest_test_anonymous", # required for NOTIFY
|
|
||||||
}
|
|
||||||
|
|
||||||
with run(env=env) as postgrest:
|
|
||||||
response = postgrest.session.get("/authors_only", headers=headers)
|
|
||||||
assert response.status_code == 401
|
|
||||||
|
|
||||||
# change external file
|
|
||||||
external_secret_file.write_text(SECRET)
|
|
||||||
|
|
||||||
# SIGUSR1 doesn't reload external files, at least when db-config=false
|
|
||||||
postgrest.process.send_signal(signal.SIGUSR1)
|
|
||||||
sleep_until_postgrest_scache_reload()
|
|
||||||
|
|
||||||
response = postgrest.session.get("/authors_only", headers=headers)
|
|
||||||
assert response.status_code == 401
|
|
||||||
|
|
||||||
# reload config and external file with SIGUSR2
|
|
||||||
postgrest.process.send_signal(signal.SIGUSR2)
|
|
||||||
sleep_until_postgrest_config_reload()
|
|
||||||
|
|
||||||
response = postgrest.session.get("/authors_only", headers=headers)
|
|
||||||
assert response.status_code == 200
|
|
||||||
|
|
||||||
# change external file to wrong value again
|
|
||||||
external_secret_file.write_text("invalid" * 5)
|
|
||||||
|
|
||||||
# reload config and external file with NOTIFY
|
|
||||||
response = postgrest.session.post("/rpc/reload_pgrst_config")
|
|
||||||
assert response.text == ""
|
|
||||||
assert response.status_code == 204
|
|
||||||
sleep_until_postgrest_config_reload()
|
|
||||||
|
|
||||||
response = postgrest.session.get("/authors_only", headers=headers)
|
|
||||||
assert response.status_code == 401
|
|
||||||
|
|
||||||
|
|
||||||
def test_invalidate_jwt_cache_when_secret_changes(tmp_path, defaultenv):
|
|
||||||
"JWT cache should be emptied after jwt-secret is changed in a config reload"
|
|
||||||
|
|
||||||
headers = jwtauthheader({"role": "postgrest_test_author"}, SECRET)
|
|
||||||
|
|
||||||
external_secret_file = tmp_path / "jwt-secret-config"
|
|
||||||
external_secret_file.write_text(SECRET)
|
|
||||||
|
|
||||||
env = {
|
|
||||||
**defaultenv,
|
|
||||||
"PGRST_JWT_SECRET": f"@{external_secret_file}",
|
|
||||||
"PGRST_DB_CHANNEL_ENABLED": "true",
|
|
||||||
"PGRST_JWT_CACHE_MAX_ENTRIES": "86400", # enable cache
|
|
||||||
"PGRST_DB_ANON_ROLE": "postgrest_test_anonymous", # required for NOTIFY
|
|
||||||
}
|
|
||||||
|
|
||||||
with run(env=env) as postgrest:
|
|
||||||
response = postgrest.session.get("/authors_only", headers=headers)
|
|
||||||
assert response.status_code == 200 # jwt gets cached
|
|
||||||
|
|
||||||
# change external file
|
|
||||||
external_secret_file.write_text("invalid" * 5)
|
|
||||||
|
|
||||||
# reload config and external file with NOTIFY
|
|
||||||
# jwt-cache should get empty
|
|
||||||
response = postgrest.session.post("/rpc/reload_pgrst_config")
|
|
||||||
assert response.text == ""
|
|
||||||
assert response.status_code == 204
|
|
||||||
sleep_until_postgrest_config_reload()
|
|
||||||
|
|
||||||
# now the request should fail because the cached token is removed
|
|
||||||
response = postgrest.session.get("/authors_only", headers=headers)
|
|
||||||
assert response.status_code == 401
|
|
||||||
|
|||||||
+1
-386
@@ -7,7 +7,7 @@ import time
|
|||||||
import pytest
|
import pytest
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from config import CONFIGSDIR, SECRET
|
from config import SECRET
|
||||||
from util import (
|
from util import (
|
||||||
Thread,
|
Thread,
|
||||||
jwtauthheader,
|
jwtauthheader,
|
||||||
@@ -25,9 +25,6 @@ from postgrest import (
|
|||||||
run,
|
run,
|
||||||
run_pgproxy,
|
run_pgproxy,
|
||||||
set_statement_timeout,
|
set_statement_timeout,
|
||||||
sleep_until_postgrest_config_reload,
|
|
||||||
sleep_until_postgrest_full_reload,
|
|
||||||
sleep_until_postgrest_scache_reload,
|
|
||||||
wait_until_exit,
|
wait_until_exit,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -229,161 +226,6 @@ def test_so_reuseport_defaults_to_false(defaultenv):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def test_db_schema_reload(tmp_path, defaultenv):
|
|
||||||
"DB schema should be reloaded from file when PostgREST is sent SIGUSR2."
|
|
||||||
config = (CONFIGSDIR / "sigusr2-settings.config").read_text()
|
|
||||||
configfile = tmp_path / "test.config"
|
|
||||||
configfile.write_text(config)
|
|
||||||
|
|
||||||
with run(configfile, env=defaultenv) as postgrest:
|
|
||||||
response = postgrest.session.get("/rpc/get_guc_value?name=search_path")
|
|
||||||
assert response.text == '"\\"public\\", \\"public\\""'
|
|
||||||
|
|
||||||
# change setting
|
|
||||||
configfile.write_text(
|
|
||||||
config.replace('db-schemas = "public"', 'db-schemas = "v1"')
|
|
||||||
)
|
|
||||||
|
|
||||||
# reload config
|
|
||||||
postgrest.process.send_signal(signal.SIGUSR2)
|
|
||||||
sleep_until_postgrest_config_reload()
|
|
||||||
|
|
||||||
# reload schema cache to verify that the config reload actually happened
|
|
||||||
postgrest.process.send_signal(signal.SIGUSR1)
|
|
||||||
sleep_until_postgrest_scache_reload()
|
|
||||||
|
|
||||||
response = postgrest.session.get("/rpc/get_guc_value?name=search_path")
|
|
||||||
assert response.text == '"\\"v1\\", \\"public\\""'
|
|
||||||
|
|
||||||
|
|
||||||
def test_db_schema_notify_reload(defaultenv):
|
|
||||||
"DB schema and config should be reloaded when PostgREST is sent a NOTIFY"
|
|
||||||
|
|
||||||
env = {**defaultenv, "PGRST_DB_CONFIG": "true", "PGRST_DB_CHANNEL_ENABLED": "true"}
|
|
||||||
|
|
||||||
with run(env=env) as postgrest:
|
|
||||||
response = postgrest.session.get("/rpc/get_guc_value?name=search_path")
|
|
||||||
assert response.text == '"\\"public\\", \\"public\\""'
|
|
||||||
|
|
||||||
# change db-schemas config on the db and reload config and cache with notify
|
|
||||||
postgrest.session.post(
|
|
||||||
"/rpc/change_db_schema_and_full_reload", data={"schemas": "v1"}
|
|
||||||
)
|
|
||||||
|
|
||||||
sleep_until_postgrest_full_reload()
|
|
||||||
|
|
||||||
response = postgrest.session.get("/rpc/get_guc_value?name=search_path")
|
|
||||||
assert response.text == '"\\"v1\\", \\"public\\""'
|
|
||||||
|
|
||||||
# reset db-schemas config on the db
|
|
||||||
response = postgrest.session.post("/rpc/reset_db_schema_config")
|
|
||||||
assert response.text == ""
|
|
||||||
assert response.status_code == 204
|
|
||||||
|
|
||||||
|
|
||||||
def test_max_rows_reload(defaultenv):
|
|
||||||
"max-rows should be reloaded from role settings when PostgREST receives a SIGUSR2."
|
|
||||||
env = {
|
|
||||||
**defaultenv,
|
|
||||||
"PGRST_DB_CONFIG": "true",
|
|
||||||
}
|
|
||||||
|
|
||||||
with run(env=env) as postgrest:
|
|
||||||
response = postgrest.session.head("/projects")
|
|
||||||
assert response.status_code == 200
|
|
||||||
assert response.headers["Content-Range"] == "0-4/*"
|
|
||||||
|
|
||||||
# change max-rows config on the db
|
|
||||||
postgrest.session.post("/rpc/change_max_rows_config", data={"val": 1})
|
|
||||||
|
|
||||||
# reload config
|
|
||||||
postgrest.process.send_signal(signal.SIGUSR2)
|
|
||||||
|
|
||||||
sleep_until_postgrest_config_reload()
|
|
||||||
|
|
||||||
response = postgrest.session.head("/projects")
|
|
||||||
assert response.status_code == 200
|
|
||||||
assert response.headers["Content-Range"] == "0-0/*"
|
|
||||||
|
|
||||||
# reset max-rows config on the db
|
|
||||||
response = postgrest.session.post("/rpc/reset_max_rows_config")
|
|
||||||
assert response.text == ""
|
|
||||||
assert response.status_code == 204
|
|
||||||
|
|
||||||
|
|
||||||
def test_max_rows_notify_reload(defaultenv):
|
|
||||||
"max-rows should be reloaded from role settings when PostgREST receives a NOTIFY"
|
|
||||||
|
|
||||||
env = {
|
|
||||||
**defaultenv,
|
|
||||||
"PGRST_DB_CONFIG": "true",
|
|
||||||
"PGRST_DB_CHANNEL_ENABLED": "true",
|
|
||||||
}
|
|
||||||
|
|
||||||
with run(env=env) as postgrest:
|
|
||||||
response = postgrest.session.head("/projects")
|
|
||||||
assert response.status_code == 200
|
|
||||||
assert response.headers["Content-Range"] == "0-4/*"
|
|
||||||
|
|
||||||
# change max-rows config on the db and reload with notify
|
|
||||||
postgrest.session.post(
|
|
||||||
"/rpc/change_max_rows_config", data={"val": 1, "notify": True}
|
|
||||||
)
|
|
||||||
|
|
||||||
sleep_until_postgrest_config_reload()
|
|
||||||
|
|
||||||
response = postgrest.session.head("/projects")
|
|
||||||
assert response.status_code == 200
|
|
||||||
assert response.headers["Content-Range"] == "0-0/*"
|
|
||||||
|
|
||||||
# reset max-rows config on the db
|
|
||||||
response = postgrest.session.post("/rpc/reset_max_rows_config")
|
|
||||||
assert response.text == ""
|
|
||||||
assert response.status_code == 204
|
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_role_claim_key_notify_reload(defaultenv):
|
|
||||||
"NOTIFY reload config should show an error if role-claim-key is invalid"
|
|
||||||
|
|
||||||
env = {
|
|
||||||
**defaultenv,
|
|
||||||
"PGRST_DB_CONFIG": "true",
|
|
||||||
"PGRST_DB_CHANNEL_ENABLED": "true",
|
|
||||||
"PGRST_LOG_LEVEL": "crit",
|
|
||||||
}
|
|
||||||
|
|
||||||
with run(env=env) as postgrest:
|
|
||||||
postgrest.session.post("/rpc/invalid_role_claim_key_reload")
|
|
||||||
|
|
||||||
output = postgrest.read_stdout()
|
|
||||||
assert 'Received a config reload message on the "pgrst" channel' in output[0]
|
|
||||||
output = postgrest.read_stdout()
|
|
||||||
assert "failed to parse role-claim-key value" in output[0]
|
|
||||||
|
|
||||||
response = postgrest.session.post("/rpc/reset_invalid_role_claim_key")
|
|
||||||
assert response.text == ""
|
|
||||||
assert response.status_code == 204
|
|
||||||
|
|
||||||
|
|
||||||
def test_notify_do_nothing(defaultenv):
|
|
||||||
"NOTIFY with unknown message should do nothing"
|
|
||||||
|
|
||||||
env = {
|
|
||||||
**defaultenv,
|
|
||||||
"PGRST_DB_CONFIG": "true",
|
|
||||||
"PGRST_DB_CHANNEL_ENABLED": "true",
|
|
||||||
"PGRST_LOG_LEVEL": "crit",
|
|
||||||
}
|
|
||||||
|
|
||||||
with run(env=env) as postgrest:
|
|
||||||
response = postgrest.session.post("/rpc/notify_do_nothing")
|
|
||||||
assert response.text == ""
|
|
||||||
assert response.status_code == 204
|
|
||||||
|
|
||||||
output = postgrest.read_stdout()
|
|
||||||
assert output == []
|
|
||||||
|
|
||||||
|
|
||||||
def test_pool_size(defaultenv, metapostgrest):
|
def test_pool_size(defaultenv, metapostgrest):
|
||||||
"Verify that PGRST_DB_POOL setting allows the correct number of parallel requests"
|
"Verify that PGRST_DB_POOL setting allows the correct number of parallel requests"
|
||||||
|
|
||||||
@@ -862,113 +704,6 @@ def test_no_pool_connection_required_on_bad_embedding(defaultenv):
|
|||||||
assert response.status_code == 400
|
assert response.status_code == 400
|
||||||
|
|
||||||
|
|
||||||
# https://github.com/PostgREST/postgrest/issues/2620
|
|
||||||
def test_notify_reloading_catalog_cache(defaultenv):
|
|
||||||
"notify should reload the connection catalog cache"
|
|
||||||
|
|
||||||
with run(env=defaultenv) as postgrest:
|
|
||||||
# first the id col is an uuid
|
|
||||||
response = postgrest.session.get(
|
|
||||||
"/cats?id=eq.dea27321-f988-4a57-93e4-8eeb38f3cf1e"
|
|
||||||
)
|
|
||||||
assert response.status_code == 200
|
|
||||||
|
|
||||||
# change it to a bigint
|
|
||||||
response = postgrest.session.post("/rpc/drop_change_cats")
|
|
||||||
assert response.text == ""
|
|
||||||
assert response.status_code == 204
|
|
||||||
sleep_until_postgrest_scache_reload()
|
|
||||||
|
|
||||||
# next request should succeed with a bigint value
|
|
||||||
response = postgrest.session.get("/cats?id=eq.1")
|
|
||||||
assert response.status_code == 200
|
|
||||||
|
|
||||||
|
|
||||||
def test_stale_schema_cache_dropped_table_returns_database_error(defaultenv):
|
|
||||||
"dropped table should return a database error while schema cache is stale"
|
|
||||||
|
|
||||||
internal_sleep = 2
|
|
||||||
env = {
|
|
||||||
**defaultenv,
|
|
||||||
"PGRST_DB_POOL": "2",
|
|
||||||
"PGRST_DB_CHANNEL_ENABLED": "true",
|
|
||||||
"PGRST_INTERNAL_SCHEMA_CACHE_QUERY_SLEEP": str(internal_sleep * 1000),
|
|
||||||
}
|
|
||||||
|
|
||||||
try:
|
|
||||||
psql_as_superuser("""
|
|
||||||
drop table if exists stale_schema_cache_items;
|
|
||||||
create table stale_schema_cache_items(id int primary key);
|
|
||||||
insert into stale_schema_cache_items values (1);
|
|
||||||
grant select on stale_schema_cache_items to postgrest_test_anonymous;
|
|
||||||
""")
|
|
||||||
|
|
||||||
with run(env=env, wait_max_seconds=10) as postgrest:
|
|
||||||
response = postgrest.session.get("/stale_schema_cache_items")
|
|
||||||
assert response.status_code == 200
|
|
||||||
|
|
||||||
psql_as_superuser("""
|
|
||||||
drop table stale_schema_cache_items;
|
|
||||||
notify pgrst, 'reload schema';
|
|
||||||
""")
|
|
||||||
|
|
||||||
response = postgrest.session.get("/stale_schema_cache_items")
|
|
||||||
payload = response.json()
|
|
||||||
assert response.status_code == 404
|
|
||||||
assert payload["code"] == "42P01"
|
|
||||||
assert (
|
|
||||||
payload["message"]
|
|
||||||
== 'relation "public.stale_schema_cache_items" does not exist'
|
|
||||||
)
|
|
||||||
|
|
||||||
time.sleep(internal_sleep + 0.3)
|
|
||||||
|
|
||||||
response = postgrest.session.get("/stale_schema_cache_items")
|
|
||||||
payload = response.json()
|
|
||||||
assert response.status_code == 404
|
|
||||||
assert payload["code"] == "PGRST205"
|
|
||||||
assert (
|
|
||||||
payload["message"]
|
|
||||||
== "Could not find the table 'public.stale_schema_cache_items' in the schema cache"
|
|
||||||
)
|
|
||||||
finally:
|
|
||||||
psql_as_superuser("drop table if exists stale_schema_cache_items;")
|
|
||||||
|
|
||||||
|
|
||||||
def test_schema_cache_concurrent_notifications(slow_schema_cache_env):
|
|
||||||
"schema cache should be up-to-date whenever a notification is sent while another reload is in progress, see https://github.com/PostgREST/postgrest/issues/2791"
|
|
||||||
|
|
||||||
internal_sleep = (
|
|
||||||
int(slow_schema_cache_env["PGRST_INTERNAL_SCHEMA_CACHE_QUERY_SLEEP"]) / 1000
|
|
||||||
)
|
|
||||||
|
|
||||||
with run(env=slow_schema_cache_env, wait_for=None) as postgrest:
|
|
||||||
time.sleep(2 * internal_sleep + 0.1) # wait for readiness manually
|
|
||||||
|
|
||||||
# first request, create a function and set a schema cache reload in progress
|
|
||||||
response = postgrest.session.post("/rpc/create_function")
|
|
||||||
assert response.text == ""
|
|
||||||
assert response.status_code == 204
|
|
||||||
|
|
||||||
time.sleep(
|
|
||||||
internal_sleep / 2
|
|
||||||
) # wait to be inside the schema cache reload process
|
|
||||||
|
|
||||||
# second request, change the same function and do another schema cache reload
|
|
||||||
response = postgrest.session.post("/rpc/migrate_function")
|
|
||||||
assert response.text == ""
|
|
||||||
assert response.status_code == 204
|
|
||||||
|
|
||||||
time.sleep(
|
|
||||||
2 * internal_sleep
|
|
||||||
) # wait enough time to get the final schema cache state
|
|
||||||
|
|
||||||
# confirm the schema cache is up-to-date and the 2nd reload wasn't lost
|
|
||||||
response = postgrest.session.get("/rpc/mult_them?c=3&d=4")
|
|
||||||
assert response.text == "12"
|
|
||||||
assert response.status_code == 200
|
|
||||||
|
|
||||||
|
|
||||||
def test_schema_cache_query_sleep_logs(defaultenv):
|
def test_schema_cache_query_sleep_logs(defaultenv):
|
||||||
"""Schema cache sleep should be reflected in the logged query duration."""
|
"""Schema cache sleep should be reflected in the logged query duration."""
|
||||||
|
|
||||||
@@ -1268,31 +1003,6 @@ def test_log_error_when_schema_cache_load_error_on_startup_to_stderr(defaultenv)
|
|||||||
assert any(log_err_message in line for line in output_start)
|
assert any(log_err_message in line for line in output_start)
|
||||||
|
|
||||||
|
|
||||||
def test_no_double_schema_cache_reload_on_empty_schema(defaultenv):
|
|
||||||
"Should only load the schema cache once when there's an empty schema cache on startup"
|
|
||||||
|
|
||||||
env = {
|
|
||||||
**defaultenv,
|
|
||||||
"PGRST_INTERNAL_SCHEMA_CACHE_QUERY_SLEEP": "300",
|
|
||||||
}
|
|
||||||
|
|
||||||
with run(env=env, wait_for=None) as postgrest:
|
|
||||||
postgrest.wait_until_scache_starts_loading()
|
|
||||||
|
|
||||||
with pytest.raises(requests.ConnectionError):
|
|
||||||
postgrest.session.get("/projects")
|
|
||||||
|
|
||||||
# Should wait enough time to load the schema cache twice to guarantee that the test is valid
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
response = postgrest.session.get("/projects")
|
|
||||||
assert response.status_code == 200
|
|
||||||
|
|
||||||
response = postgrest.admin.get("/metrics")
|
|
||||||
assert response.status_code == 200
|
|
||||||
assert 'pgrst_schema_cache_loads_total{status="SUCCESS"} 1.0' in response.text
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("level", ["crit", "error", "warn", "info", "debug"])
|
@pytest.mark.parametrize("level", ["crit", "error", "warn", "info", "debug"])
|
||||||
def test_log_pool_req_observation(level, defaultenv):
|
def test_log_pool_req_observation(level, defaultenv):
|
||||||
"PostgREST should log PoolRequest and PoolRequestFullfilled observation when log-level=debug"
|
"PostgREST should log PoolRequest and PoolRequestFullfilled observation when log-level=debug"
|
||||||
@@ -1458,98 +1168,3 @@ def test_use_legacy_target_names(enabled, defaultenv):
|
|||||||
else:
|
else:
|
||||||
assert response.status_code == 400
|
assert response.status_code == 400
|
||||||
assert not has_warning_log and not has_hint_log
|
assert not has_warning_log and not has_hint_log
|
||||||
|
|
||||||
|
|
||||||
def test_config_log_level_is_reloadable(tmp_path, defaultenv):
|
|
||||||
"Config log-level should be reloadable on SIGUSR2"
|
|
||||||
|
|
||||||
config = (CONFIGSDIR / "sigusr2-settings.config").read_text()
|
|
||||||
configfile = tmp_path / "test.config"
|
|
||||||
configfile.write_text(config)
|
|
||||||
|
|
||||||
# Delete the env variable for "log-level" so the config file value isn't overridden
|
|
||||||
del defaultenv["PGRST_LOG_LEVEL"]
|
|
||||||
|
|
||||||
with run(configfile, env=defaultenv) as postgrest:
|
|
||||||
response = postgrest.session.get("/projects")
|
|
||||||
assert response.status_code == 200
|
|
||||||
output = postgrest.read_stdout(nlines=5)
|
|
||||||
|
|
||||||
# log-level = error, so this log line shouldn't be logged
|
|
||||||
assert not any(
|
|
||||||
"Trying to borrow a connection from pool" in line for line in output
|
|
||||||
)
|
|
||||||
|
|
||||||
# change setting
|
|
||||||
configfile.write_text(
|
|
||||||
config.replace('log-level = "error"', 'log-level = "debug"')
|
|
||||||
)
|
|
||||||
# reload
|
|
||||||
postgrest.process.send_signal(signal.SIGUSR2)
|
|
||||||
|
|
||||||
sleep_until_postgrest_config_reload()
|
|
||||||
|
|
||||||
response = postgrest.session.get("/projects")
|
|
||||||
assert response.status_code == 200
|
|
||||||
output = postgrest.read_stdout(nlines=5)
|
|
||||||
|
|
||||||
# log-level = debug now, so this log line must be logged
|
|
||||||
assert any("Trying to borrow a connection from pool" in line for line in output)
|
|
||||||
|
|
||||||
|
|
||||||
def test_config_db_channel_enabled_is_reloadable(tmp_path, defaultenv):
|
|
||||||
"Config db-channel-enabled should be reloadable on SIGUSR2"
|
|
||||||
|
|
||||||
config = (CONFIGSDIR / "sigusr2-settings.config").read_text()
|
|
||||||
configfile = tmp_path / "test.config"
|
|
||||||
configfile.write_text(config)
|
|
||||||
|
|
||||||
with run(configfile, env=defaultenv, no_startup_stdout=False) as postgrest:
|
|
||||||
output = postgrest.read_stdout(nlines=7)
|
|
||||||
|
|
||||||
# db-channel-enabled = false, so this shouldn't be logged
|
|
||||||
assert not any(
|
|
||||||
f'"{defaultenv["PGHOST"]}:5432" and listening for database notifications on the "pgrst" channel'
|
|
||||||
in line
|
|
||||||
for line in output
|
|
||||||
)
|
|
||||||
|
|
||||||
# change setting
|
|
||||||
configfile.write_text(
|
|
||||||
config.replace(
|
|
||||||
'db-channel-enabled = "false"', 'db-channel-enabled = "true"'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
# reload
|
|
||||||
postgrest.process.send_signal(signal.SIGUSR2)
|
|
||||||
sleep_until_postgrest_config_reload()
|
|
||||||
|
|
||||||
output = postgrest.read_stdout(nlines=7)
|
|
||||||
|
|
||||||
# db-channel-enabled = true, so this logged
|
|
||||||
assert any(
|
|
||||||
f'"{defaultenv["PGHOST"]}:5432" and listening for database notifications on the "pgrst" channel'
|
|
||||||
in line
|
|
||||||
for line in output
|
|
||||||
)
|
|
||||||
|
|
||||||
# change setting back to false
|
|
||||||
configfile.write_text(
|
|
||||||
configfile.read_text().replace(
|
|
||||||
'db-channel-enabled = "true"', 'db-channel-enabled = "false"'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
# reload
|
|
||||||
postgrest.process.send_signal(signal.SIGUSR2)
|
|
||||||
sleep_until_postgrest_config_reload()
|
|
||||||
|
|
||||||
output = postgrest.read_stdout(nlines=7)
|
|
||||||
|
|
||||||
# db-channel-enabled = false, so this shouldn't be logged
|
|
||||||
assert not any(
|
|
||||||
f'"{defaultenv["PGHOST"]}:5432" and listening for database notifications on the "pgrst" channel'
|
|
||||||
in line
|
|
||||||
for line in output
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -0,0 +1,508 @@
|
|||||||
|
"Test reloading behaviors in PostgREST"
|
||||||
|
|
||||||
|
import signal
|
||||||
|
import time
|
||||||
|
import pytest
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from config import CONFIGSDIR, SECRET
|
||||||
|
from util import (
|
||||||
|
jwtauthheader,
|
||||||
|
psql_as_superuser,
|
||||||
|
)
|
||||||
|
from postgrest import (
|
||||||
|
run,
|
||||||
|
sleep_until_postgrest_config_reload,
|
||||||
|
sleep_until_postgrest_full_reload,
|
||||||
|
sleep_until_postgrest_scache_reload,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_db_schema_notify_reload(defaultenv):
|
||||||
|
"DB schema and config should be reloaded when PostgREST is sent a NOTIFY"
|
||||||
|
|
||||||
|
env = {**defaultenv, "PGRST_DB_CONFIG": "true", "PGRST_DB_CHANNEL_ENABLED": "true"}
|
||||||
|
|
||||||
|
with run(env=env) as postgrest:
|
||||||
|
response = postgrest.session.get("/rpc/get_guc_value?name=search_path")
|
||||||
|
assert response.text == '"\\"public\\", \\"public\\""'
|
||||||
|
|
||||||
|
# change db-schemas config on the db and reload config and cache with notify
|
||||||
|
postgrest.session.post(
|
||||||
|
"/rpc/change_db_schema_and_full_reload", data={"schemas": "v1"}
|
||||||
|
)
|
||||||
|
|
||||||
|
sleep_until_postgrest_full_reload()
|
||||||
|
|
||||||
|
response = postgrest.session.get("/rpc/get_guc_value?name=search_path")
|
||||||
|
assert response.text == '"\\"v1\\", \\"public\\""'
|
||||||
|
|
||||||
|
# reset db-schemas config on the db
|
||||||
|
response = postgrest.session.post("/rpc/reset_db_schema_config")
|
||||||
|
assert response.text == ""
|
||||||
|
assert response.status_code == 204
|
||||||
|
|
||||||
|
|
||||||
|
def test_db_schema_reload(tmp_path, defaultenv):
|
||||||
|
"DB schema should be reloaded from file when PostgREST is sent SIGUSR2."
|
||||||
|
config = (CONFIGSDIR / "sigusr2-settings.config").read_text()
|
||||||
|
configfile = tmp_path / "test.config"
|
||||||
|
configfile.write_text(config)
|
||||||
|
|
||||||
|
with run(configfile, env=defaultenv) as postgrest:
|
||||||
|
response = postgrest.session.get("/rpc/get_guc_value?name=search_path")
|
||||||
|
assert response.text == '"\\"public\\", \\"public\\""'
|
||||||
|
|
||||||
|
# change setting
|
||||||
|
configfile.write_text(
|
||||||
|
config.replace('db-schemas = "public"', 'db-schemas = "v1"')
|
||||||
|
)
|
||||||
|
|
||||||
|
# reload config
|
||||||
|
postgrest.process.send_signal(signal.SIGUSR2)
|
||||||
|
sleep_until_postgrest_config_reload()
|
||||||
|
|
||||||
|
# reload schema cache to verify that the config reload actually happened
|
||||||
|
postgrest.process.send_signal(signal.SIGUSR1)
|
||||||
|
sleep_until_postgrest_scache_reload()
|
||||||
|
|
||||||
|
response = postgrest.session.get("/rpc/get_guc_value?name=search_path")
|
||||||
|
assert response.text == '"\\"v1\\", \\"public\\""'
|
||||||
|
|
||||||
|
|
||||||
|
def test_invalid_role_claim_key_notify_reload(defaultenv):
|
||||||
|
"NOTIFY reload config should show an error if role-claim-key is invalid"
|
||||||
|
|
||||||
|
env = {
|
||||||
|
**defaultenv,
|
||||||
|
"PGRST_DB_CONFIG": "true",
|
||||||
|
"PGRST_DB_CHANNEL_ENABLED": "true",
|
||||||
|
"PGRST_LOG_LEVEL": "crit",
|
||||||
|
}
|
||||||
|
|
||||||
|
with run(env=env) as postgrest:
|
||||||
|
postgrest.session.post("/rpc/invalid_role_claim_key_reload")
|
||||||
|
|
||||||
|
output = postgrest.read_stdout()
|
||||||
|
assert 'Received a config reload message on the "pgrst" channel' in output[0]
|
||||||
|
output = postgrest.read_stdout()
|
||||||
|
assert "failed to parse role-claim-key value" in output[0]
|
||||||
|
|
||||||
|
response = postgrest.session.post("/rpc/reset_invalid_role_claim_key")
|
||||||
|
assert response.text == ""
|
||||||
|
assert response.status_code == 204
|
||||||
|
|
||||||
|
|
||||||
|
def test_max_rows_reload(defaultenv):
|
||||||
|
"max-rows should be reloaded from role settings when PostgREST receives a SIGUSR2."
|
||||||
|
env = {
|
||||||
|
**defaultenv,
|
||||||
|
"PGRST_DB_CONFIG": "true",
|
||||||
|
}
|
||||||
|
|
||||||
|
with run(env=env) as postgrest:
|
||||||
|
response = postgrest.session.head("/projects")
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.headers["Content-Range"] == "0-4/*"
|
||||||
|
|
||||||
|
# change max-rows config on the db
|
||||||
|
postgrest.session.post("/rpc/change_max_rows_config", data={"val": 1})
|
||||||
|
|
||||||
|
# reload config
|
||||||
|
postgrest.process.send_signal(signal.SIGUSR2)
|
||||||
|
|
||||||
|
sleep_until_postgrest_config_reload()
|
||||||
|
|
||||||
|
response = postgrest.session.head("/projects")
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.headers["Content-Range"] == "0-0/*"
|
||||||
|
|
||||||
|
# reset max-rows config on the db
|
||||||
|
response = postgrest.session.post("/rpc/reset_max_rows_config")
|
||||||
|
assert response.text == ""
|
||||||
|
assert response.status_code == 204
|
||||||
|
|
||||||
|
|
||||||
|
def test_max_rows_notify_reload(defaultenv):
|
||||||
|
"max-rows should be reloaded from role settings when PostgREST receives a NOTIFY"
|
||||||
|
|
||||||
|
env = {
|
||||||
|
**defaultenv,
|
||||||
|
"PGRST_DB_CONFIG": "true",
|
||||||
|
"PGRST_DB_CHANNEL_ENABLED": "true",
|
||||||
|
}
|
||||||
|
|
||||||
|
with run(env=env) as postgrest:
|
||||||
|
response = postgrest.session.head("/projects")
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.headers["Content-Range"] == "0-4/*"
|
||||||
|
|
||||||
|
# change max-rows config on the db and reload with notify
|
||||||
|
postgrest.session.post(
|
||||||
|
"/rpc/change_max_rows_config", data={"val": 1, "notify": True}
|
||||||
|
)
|
||||||
|
|
||||||
|
sleep_until_postgrest_config_reload()
|
||||||
|
|
||||||
|
response = postgrest.session.head("/projects")
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.headers["Content-Range"] == "0-0/*"
|
||||||
|
|
||||||
|
# reset max-rows config on the db
|
||||||
|
response = postgrest.session.post("/rpc/reset_max_rows_config")
|
||||||
|
assert response.text == ""
|
||||||
|
assert response.status_code == 204
|
||||||
|
|
||||||
|
|
||||||
|
def test_no_double_schema_cache_reload_on_empty_schema(defaultenv):
|
||||||
|
"Should only load the schema cache once when there's an empty schema cache on startup"
|
||||||
|
|
||||||
|
env = {
|
||||||
|
**defaultenv,
|
||||||
|
"PGRST_INTERNAL_SCHEMA_CACHE_QUERY_SLEEP": "300",
|
||||||
|
}
|
||||||
|
|
||||||
|
with run(env=env, wait_for=None) as postgrest:
|
||||||
|
postgrest.wait_until_scache_starts_loading()
|
||||||
|
|
||||||
|
with pytest.raises(requests.ConnectionError):
|
||||||
|
postgrest.session.get("/projects")
|
||||||
|
|
||||||
|
# Should wait enough time to load the schema cache twice to guarantee that the test is valid
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
response = postgrest.session.get("/projects")
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
response = postgrest.admin.get("/metrics")
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert 'pgrst_schema_cache_loads_total{status="SUCCESS"} 1.0' in response.text
|
||||||
|
|
||||||
|
|
||||||
|
# https://github.com/PostgREST/postgrest/issues/2620
|
||||||
|
def test_notify_reloading_catalog_cache(defaultenv):
|
||||||
|
"notify should reload the connection catalog cache"
|
||||||
|
|
||||||
|
with run(env=defaultenv) as postgrest:
|
||||||
|
# first the id col is an uuid
|
||||||
|
response = postgrest.session.get(
|
||||||
|
"/cats?id=eq.dea27321-f988-4a57-93e4-8eeb38f3cf1e"
|
||||||
|
)
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
# change it to a bigint
|
||||||
|
response = postgrest.session.post("/rpc/drop_change_cats")
|
||||||
|
assert response.text == ""
|
||||||
|
assert response.status_code == 204
|
||||||
|
sleep_until_postgrest_scache_reload()
|
||||||
|
|
||||||
|
# next request should succeed with a bigint value
|
||||||
|
response = postgrest.session.get("/cats?id=eq.1")
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_notify_do_nothing(defaultenv):
|
||||||
|
"NOTIFY with unknown message should do nothing"
|
||||||
|
|
||||||
|
env = {
|
||||||
|
**defaultenv,
|
||||||
|
"PGRST_DB_CONFIG": "true",
|
||||||
|
"PGRST_DB_CHANNEL_ENABLED": "true",
|
||||||
|
"PGRST_LOG_LEVEL": "crit",
|
||||||
|
}
|
||||||
|
|
||||||
|
with run(env=env) as postgrest:
|
||||||
|
response = postgrest.session.post("/rpc/notify_do_nothing")
|
||||||
|
assert response.text == ""
|
||||||
|
assert response.status_code == 204
|
||||||
|
|
||||||
|
output = postgrest.read_stdout()
|
||||||
|
assert output == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_schema_cache_concurrent_notifications(slow_schema_cache_env):
|
||||||
|
"schema cache should be up-to-date whenever a notification is sent while another reload is in progress, see https://github.com/PostgREST/postgrest/issues/2791"
|
||||||
|
|
||||||
|
internal_sleep = (
|
||||||
|
int(slow_schema_cache_env["PGRST_INTERNAL_SCHEMA_CACHE_QUERY_SLEEP"]) / 1000
|
||||||
|
)
|
||||||
|
|
||||||
|
with run(env=slow_schema_cache_env, wait_for=None) as postgrest:
|
||||||
|
time.sleep(2 * internal_sleep + 0.1) # wait for readiness manually
|
||||||
|
|
||||||
|
# first request, create a function and set a schema cache reload in progress
|
||||||
|
response = postgrest.session.post("/rpc/create_function")
|
||||||
|
assert response.text == ""
|
||||||
|
assert response.status_code == 204
|
||||||
|
|
||||||
|
time.sleep(
|
||||||
|
internal_sleep / 2
|
||||||
|
) # wait to be inside the schema cache reload process
|
||||||
|
|
||||||
|
# second request, change the same function and do another schema cache reload
|
||||||
|
response = postgrest.session.post("/rpc/migrate_function")
|
||||||
|
assert response.text == ""
|
||||||
|
assert response.status_code == 204
|
||||||
|
|
||||||
|
time.sleep(
|
||||||
|
2 * internal_sleep
|
||||||
|
) # wait enough time to get the final schema cache state
|
||||||
|
|
||||||
|
# confirm the schema cache is up-to-date and the 2nd reload wasn't lost
|
||||||
|
response = postgrest.session.get("/rpc/mult_them?c=3&d=4")
|
||||||
|
assert response.text == "12"
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_jwt_secret_reload(tmp_path, defaultenv):
|
||||||
|
"JWT secret should be reloaded from file when PostgREST is sent SIGUSR2."
|
||||||
|
config = (CONFIGSDIR / "sigusr2-settings.config").read_text()
|
||||||
|
configfile = tmp_path / "test.config"
|
||||||
|
configfile.write_text(config)
|
||||||
|
|
||||||
|
headers = jwtauthheader({"role": "postgrest_test_author"}, SECRET)
|
||||||
|
|
||||||
|
with run(configfile, env=defaultenv) as postgrest:
|
||||||
|
response = postgrest.session.get("/authors_only", headers=headers)
|
||||||
|
assert response.status_code == 401
|
||||||
|
|
||||||
|
# change setting
|
||||||
|
configfile.write_text(config.replace("invalid" * 5, SECRET))
|
||||||
|
|
||||||
|
# reload config
|
||||||
|
postgrest.process.send_signal(signal.SIGUSR2)
|
||||||
|
|
||||||
|
sleep_until_postgrest_config_reload()
|
||||||
|
|
||||||
|
response = postgrest.session.get("/authors_only", headers=headers)
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_jwt_secret_external_file_reload(tmp_path, defaultenv):
|
||||||
|
"JWT secret external file should be reloaded when PostgREST is sent a SIGUSR2 or a NOTIFY."
|
||||||
|
headers = jwtauthheader({"role": "postgrest_test_author"}, SECRET)
|
||||||
|
|
||||||
|
external_secret_file = tmp_path / "jwt-secret-config"
|
||||||
|
external_secret_file.write_text("invalid" * 5)
|
||||||
|
|
||||||
|
env = {
|
||||||
|
**defaultenv,
|
||||||
|
"PGRST_JWT_SECRET": f"@{external_secret_file}",
|
||||||
|
"PGRST_DB_CHANNEL_ENABLED": "true",
|
||||||
|
"PGRST_DB_CONFIG": "false",
|
||||||
|
"PGRST_DB_ANON_ROLE": "postgrest_test_anonymous", # required for NOTIFY
|
||||||
|
}
|
||||||
|
|
||||||
|
with run(env=env) as postgrest:
|
||||||
|
response = postgrest.session.get("/authors_only", headers=headers)
|
||||||
|
assert response.status_code == 401
|
||||||
|
|
||||||
|
# change external file
|
||||||
|
external_secret_file.write_text(SECRET)
|
||||||
|
|
||||||
|
# SIGUSR1 doesn't reload external files, at least when db-config=false
|
||||||
|
postgrest.process.send_signal(signal.SIGUSR1)
|
||||||
|
sleep_until_postgrest_scache_reload()
|
||||||
|
|
||||||
|
response = postgrest.session.get("/authors_only", headers=headers)
|
||||||
|
assert response.status_code == 401
|
||||||
|
|
||||||
|
# reload config and external file with SIGUSR2
|
||||||
|
postgrest.process.send_signal(signal.SIGUSR2)
|
||||||
|
sleep_until_postgrest_config_reload()
|
||||||
|
|
||||||
|
response = postgrest.session.get("/authors_only", headers=headers)
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
# change external file to wrong value again
|
||||||
|
external_secret_file.write_text("invalid" * 5)
|
||||||
|
|
||||||
|
# reload config and external file with NOTIFY
|
||||||
|
response = postgrest.session.post("/rpc/reload_pgrst_config")
|
||||||
|
assert response.text == ""
|
||||||
|
assert response.status_code == 204
|
||||||
|
sleep_until_postgrest_config_reload()
|
||||||
|
|
||||||
|
response = postgrest.session.get("/authors_only", headers=headers)
|
||||||
|
assert response.status_code == 401
|
||||||
|
|
||||||
|
|
||||||
|
def test_invalidate_jwt_cache_when_secret_changes(tmp_path, defaultenv):
|
||||||
|
"JWT cache should be emptied after jwt-secret is changed in a config reload"
|
||||||
|
|
||||||
|
headers = jwtauthheader({"role": "postgrest_test_author"}, SECRET)
|
||||||
|
|
||||||
|
external_secret_file = tmp_path / "jwt-secret-config"
|
||||||
|
external_secret_file.write_text(SECRET)
|
||||||
|
|
||||||
|
env = {
|
||||||
|
**defaultenv,
|
||||||
|
"PGRST_JWT_SECRET": f"@{external_secret_file}",
|
||||||
|
"PGRST_DB_CHANNEL_ENABLED": "true",
|
||||||
|
"PGRST_JWT_CACHE_MAX_ENTRIES": "86400", # enable cache
|
||||||
|
"PGRST_DB_ANON_ROLE": "postgrest_test_anonymous", # required for NOTIFY
|
||||||
|
}
|
||||||
|
|
||||||
|
with run(env=env) as postgrest:
|
||||||
|
response = postgrest.session.get("/authors_only", headers=headers)
|
||||||
|
assert response.status_code == 200 # jwt gets cached
|
||||||
|
|
||||||
|
# change external file
|
||||||
|
external_secret_file.write_text("invalid" * 5)
|
||||||
|
|
||||||
|
# reload config and external file with NOTIFY
|
||||||
|
# jwt-cache should get empty
|
||||||
|
response = postgrest.session.post("/rpc/reload_pgrst_config")
|
||||||
|
assert response.text == ""
|
||||||
|
assert response.status_code == 204
|
||||||
|
sleep_until_postgrest_config_reload()
|
||||||
|
|
||||||
|
# now the request should fail because the cached token is removed
|
||||||
|
response = postgrest.session.get("/authors_only", headers=headers)
|
||||||
|
assert response.status_code == 401
|
||||||
|
|
||||||
|
|
||||||
|
def test_stale_schema_cache_dropped_table_returns_database_error(defaultenv):
|
||||||
|
"dropped table should return a database error while schema cache is stale"
|
||||||
|
|
||||||
|
internal_sleep = 2
|
||||||
|
env = {
|
||||||
|
**defaultenv,
|
||||||
|
"PGRST_DB_POOL": "2",
|
||||||
|
"PGRST_DB_CHANNEL_ENABLED": "true",
|
||||||
|
"PGRST_INTERNAL_SCHEMA_CACHE_QUERY_SLEEP": str(internal_sleep * 1000),
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
psql_as_superuser("""
|
||||||
|
drop table if exists stale_schema_cache_items;
|
||||||
|
create table stale_schema_cache_items(id int primary key);
|
||||||
|
insert into stale_schema_cache_items values (1);
|
||||||
|
grant select on stale_schema_cache_items to postgrest_test_anonymous;
|
||||||
|
""")
|
||||||
|
|
||||||
|
with run(env=env, wait_max_seconds=10) as postgrest:
|
||||||
|
response = postgrest.session.get("/stale_schema_cache_items")
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
psql_as_superuser("""
|
||||||
|
drop table stale_schema_cache_items;
|
||||||
|
notify pgrst, 'reload schema';
|
||||||
|
""")
|
||||||
|
|
||||||
|
response = postgrest.session.get("/stale_schema_cache_items")
|
||||||
|
payload = response.json()
|
||||||
|
assert response.status_code == 404
|
||||||
|
assert payload["code"] == "42P01"
|
||||||
|
assert (
|
||||||
|
payload["message"]
|
||||||
|
== 'relation "public.stale_schema_cache_items" does not exist'
|
||||||
|
)
|
||||||
|
|
||||||
|
time.sleep(internal_sleep + 0.3)
|
||||||
|
|
||||||
|
response = postgrest.session.get("/stale_schema_cache_items")
|
||||||
|
payload = response.json()
|
||||||
|
assert response.status_code == 404
|
||||||
|
assert payload["code"] == "PGRST205"
|
||||||
|
assert (
|
||||||
|
payload["message"]
|
||||||
|
== "Could not find the table 'public.stale_schema_cache_items' in the schema cache"
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
psql_as_superuser("drop table if exists stale_schema_cache_items;")
|
||||||
|
|
||||||
|
|
||||||
|
def test_config_log_level_is_reloadable(tmp_path, defaultenv):
|
||||||
|
"Config log-level should be reloadable on SIGUSR2"
|
||||||
|
|
||||||
|
config = (CONFIGSDIR / "sigusr2-settings.config").read_text()
|
||||||
|
configfile = tmp_path / "test.config"
|
||||||
|
configfile.write_text(config)
|
||||||
|
|
||||||
|
# Delete the env variable for "log-level" so the config file value isn't overridden
|
||||||
|
del defaultenv["PGRST_LOG_LEVEL"]
|
||||||
|
|
||||||
|
with run(configfile, env=defaultenv) as postgrest:
|
||||||
|
response = postgrest.session.get("/projects")
|
||||||
|
assert response.status_code == 200
|
||||||
|
output = postgrest.read_stdout(nlines=5)
|
||||||
|
|
||||||
|
# log-level = error, so this log line shouldn't be logged
|
||||||
|
assert not any(
|
||||||
|
"Trying to borrow a connection from pool" in line for line in output
|
||||||
|
)
|
||||||
|
|
||||||
|
# change setting
|
||||||
|
configfile.write_text(
|
||||||
|
config.replace('log-level = "error"', 'log-level = "debug"')
|
||||||
|
)
|
||||||
|
# reload
|
||||||
|
postgrest.process.send_signal(signal.SIGUSR2)
|
||||||
|
|
||||||
|
sleep_until_postgrest_config_reload()
|
||||||
|
|
||||||
|
response = postgrest.session.get("/projects")
|
||||||
|
assert response.status_code == 200
|
||||||
|
output = postgrest.read_stdout(nlines=5)
|
||||||
|
|
||||||
|
# log-level = debug now, so this log line must be logged
|
||||||
|
assert any("Trying to borrow a connection from pool" in line for line in output)
|
||||||
|
|
||||||
|
|
||||||
|
def test_config_db_channel_enabled_is_reloadable(tmp_path, defaultenv):
|
||||||
|
"Config db-channel-enabled should be reloadable on SIGUSR2"
|
||||||
|
|
||||||
|
config = (CONFIGSDIR / "sigusr2-settings.config").read_text()
|
||||||
|
configfile = tmp_path / "test.config"
|
||||||
|
configfile.write_text(config)
|
||||||
|
|
||||||
|
with run(configfile, env=defaultenv, no_startup_stdout=False) as postgrest:
|
||||||
|
output = postgrest.read_stdout(nlines=7)
|
||||||
|
|
||||||
|
# db-channel-enabled = false, so this shouldn't be logged
|
||||||
|
assert not any(
|
||||||
|
f'"{defaultenv["PGHOST"]}:5432" and listening for database notifications on the "pgrst" channel'
|
||||||
|
in line
|
||||||
|
for line in output
|
||||||
|
)
|
||||||
|
|
||||||
|
# change setting
|
||||||
|
configfile.write_text(
|
||||||
|
config.replace(
|
||||||
|
'db-channel-enabled = "false"', 'db-channel-enabled = "true"'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# reload
|
||||||
|
postgrest.process.send_signal(signal.SIGUSR2)
|
||||||
|
sleep_until_postgrest_config_reload()
|
||||||
|
|
||||||
|
output = postgrest.read_stdout(nlines=7)
|
||||||
|
|
||||||
|
# db-channel-enabled = true, so this logged
|
||||||
|
assert any(
|
||||||
|
f'"{defaultenv["PGHOST"]}:5432" and listening for database notifications on the "pgrst" channel'
|
||||||
|
in line
|
||||||
|
for line in output
|
||||||
|
)
|
||||||
|
|
||||||
|
# change setting back to false
|
||||||
|
configfile.write_text(
|
||||||
|
configfile.read_text().replace(
|
||||||
|
'db-channel-enabled = "true"', 'db-channel-enabled = "false"'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# reload
|
||||||
|
postgrest.process.send_signal(signal.SIGUSR2)
|
||||||
|
sleep_until_postgrest_config_reload()
|
||||||
|
|
||||||
|
output = postgrest.read_stdout(nlines=7)
|
||||||
|
|
||||||
|
# db-channel-enabled = false, so this shouldn't be logged
|
||||||
|
assert not any(
|
||||||
|
f'"{defaultenv["PGHOST"]}:5432" and listening for database notifications on the "pgrst" channel'
|
||||||
|
in line
|
||||||
|
for line in output
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user