tests: add tests for statement_timeout
The new tests verify that: - statement_timeout on the authenticator role works to cancel slow statements - changes to statement_timeout take effect on SIGUSR1 This reuses the old "limited_authenticator" role and adds some plumbing to allow reliably changing the statement timeout even if the current role is not functional due to a low statement timeout, and to make tests that modify the role independent from each other. - introduce module-wide metapostgrest fixture to have an out-of-band way to manipulate the database, without having to spin up extra postgrest instances per test - reset statement_timeout at the start of the respective tests
This commit is contained in:
+10
-5
@@ -59,10 +59,15 @@ ALTER ROLE other_authenticator SET pgrst.db_extra_search_path = 'public, extensi
|
|||||||
ALTER ROLE other_authenticator SET pgrst.openapi_mode = 'disabled';
|
ALTER ROLE other_authenticator SET pgrst.openapi_mode = 'disabled';
|
||||||
ALTER ROLE other_authenticator SET pgrst.openapi_security_active = 'false';
|
ALTER ROLE other_authenticator SET pgrst.openapi_security_active = 'false';
|
||||||
|
|
||||||
-- limited authenticator used for failed schema cache loads
|
-- authenticator used for tests that manipulate statement timeout
|
||||||
CREATE ROLE limited_authenticator LOGIN NOINHERIT;
|
CREATE ROLE timeout_authenticator LOGIN NOINHERIT;
|
||||||
|
|
||||||
create or replace function no_schema_cache_for_limited_authenticator() returns void as $_$
|
create function set_statement_timeout(role text, milliseconds int) returns void as $_$
|
||||||
begin
|
begin
|
||||||
ALTER ROLE limited_authenticator SET statement_timeout to 1;
|
execute format($$
|
||||||
end $_$ volatile security definer language plpgsql ;
|
alter role %I set statement_timeout to %L;
|
||||||
|
$$, role, milliseconds);
|
||||||
|
end $_$ volatile security definer language plpgsql;
|
||||||
|
|
||||||
|
-- authenticator used for test-independent database manipulation
|
||||||
|
CREATE ROLE meta_authenticator LOGIN NOINHERIT;
|
||||||
|
|||||||
+101
-8
@@ -97,6 +97,22 @@ def defaultenv():
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="module")
|
||||||
|
def metapostgrest():
|
||||||
|
"A shared postgrest instance to use for interacting with the database independently of the instance under test"
|
||||||
|
role = "meta_authenticator"
|
||||||
|
env = {
|
||||||
|
"PGDATABASE": os.environ["PGDATABASE"],
|
||||||
|
"PGHOST": os.environ["PGHOST"],
|
||||||
|
"PGUSER": role,
|
||||||
|
"PGRST_DB_ANON_ROLE": role,
|
||||||
|
"PGRST_DB_CONFIG": "true",
|
||||||
|
"PGRST_LOG_LEVEL": "info",
|
||||||
|
}
|
||||||
|
with run(env=env) as postgrest:
|
||||||
|
yield postgrest
|
||||||
|
|
||||||
|
|
||||||
def hpctixfile():
|
def hpctixfile():
|
||||||
"Returns an individual filename for each test, if the HPCTIXFILE environment variable is set."
|
"Returns an individual filename for each test, if the HPCTIXFILE environment variable is set."
|
||||||
if "HPCTIXFILE" not in os.environ:
|
if "HPCTIXFILE" not in os.environ:
|
||||||
@@ -818,6 +834,83 @@ def test_db_prepared_statements_disable(defaultenv):
|
|||||||
assert response.text == "false"
|
assert response.text == "false"
|
||||||
|
|
||||||
|
|
||||||
|
def set_statement_timeout(postgrest, role, milliseconds):
|
||||||
|
"""Set the statement timeout for the given role.
|
||||||
|
For this to work reliably with low previous timeout settings,
|
||||||
|
use a postgrest instance that doesn't use the affected role."""
|
||||||
|
|
||||||
|
response = postgrest.session.post(
|
||||||
|
"/rpc/set_statement_timeout",
|
||||||
|
data={"role": role, "milliseconds": milliseconds}
|
||||||
|
)
|
||||||
|
assert response.status_code == 204
|
||||||
|
|
||||||
|
|
||||||
|
def reset_statement_timeout(postgrest, role):
|
||||||
|
"Reset the statement timeout for the given role to the default 0 (no timeout)"
|
||||||
|
set_statement_timeout(postgrest, role, 0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_statement_timeout(defaultenv, metapostgrest):
|
||||||
|
"Statement timeout times out slow statements"
|
||||||
|
|
||||||
|
role = "timeout_authenticator"
|
||||||
|
set_statement_timeout(metapostgrest, role, 1000) # 1 second
|
||||||
|
|
||||||
|
env = {
|
||||||
|
**defaultenv,
|
||||||
|
"PGUSER": role,
|
||||||
|
"PGRST_DB_ANON_ROLE": role,
|
||||||
|
}
|
||||||
|
|
||||||
|
with run(env=env) as postgrest:
|
||||||
|
response = postgrest.session.get("/rpc/sleep?seconds=0.5")
|
||||||
|
assert response.status_code == 204
|
||||||
|
|
||||||
|
response = postgrest.session.get("/rpc/sleep?seconds=2")
|
||||||
|
assert response.status_code == 500
|
||||||
|
data = response.json()
|
||||||
|
assert data["message"] == "canceling statement due to statement timeout"
|
||||||
|
|
||||||
|
|
||||||
|
def test_change_statement_timeout(defaultenv, metapostgrest):
|
||||||
|
"Statement timeout changes take effect immediately"
|
||||||
|
|
||||||
|
role = "timeout_authenticator"
|
||||||
|
reset_statement_timeout(metapostgrest, role)
|
||||||
|
|
||||||
|
env = {
|
||||||
|
**defaultenv,
|
||||||
|
"PGUSER": role,
|
||||||
|
"PGRST_DB_ANON_ROLE": role,
|
||||||
|
}
|
||||||
|
|
||||||
|
with run(env=env) as postgrest:
|
||||||
|
# no limit initially
|
||||||
|
response = postgrest.session.get("/rpc/sleep?seconds=1")
|
||||||
|
assert response.status_code == 204
|
||||||
|
|
||||||
|
set_statement_timeout(metapostgrest, role, 500) # 0.5s
|
||||||
|
|
||||||
|
# trigger schema refresh
|
||||||
|
postgrest.process.send_signal(signal.SIGUSR1)
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
response = postgrest.session.get("/rpc/sleep?seconds=1")
|
||||||
|
assert response.status_code == 500
|
||||||
|
data = response.json()
|
||||||
|
assert data["message"] == "canceling statement due to statement timeout"
|
||||||
|
|
||||||
|
set_statement_timeout(metapostgrest, role, 2000) # 2s
|
||||||
|
|
||||||
|
# trigger role setting refresh
|
||||||
|
postgrest.process.send_signal(signal.SIGUSR1)
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
response = postgrest.session.get("/rpc/sleep?seconds=1")
|
||||||
|
assert response.status_code == 204
|
||||||
|
|
||||||
|
|
||||||
def test_admin_ready_w_channel(defaultenv):
|
def test_admin_ready_w_channel(defaultenv):
|
||||||
"Should get a success response from the admin server ready endpoint when the LISTEN channel is enabled"
|
"Should get a success response from the admin server ready endpoint when the LISTEN channel is enabled"
|
||||||
|
|
||||||
@@ -844,22 +937,22 @@ 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):
|
def test_admin_ready_includes_schema_cache_state(defaultenv, metapostgrest):
|
||||||
"Should get a failed response from the admin server ready endpoint when the schema cache is not loaded"
|
"Should get a failed response from the admin server ready endpoint when the schema cache is not loaded"
|
||||||
|
|
||||||
|
role = "timeout_authenticator"
|
||||||
|
reset_statement_timeout(metapostgrest, role)
|
||||||
|
|
||||||
env = {
|
env = {
|
||||||
**defaultenv,
|
**defaultenv,
|
||||||
"PGUSER": "limited_authenticator",
|
"PGUSER": role,
|
||||||
"PGRST_DB_ANON_ROLE": "limited_authenticator",
|
"PGRST_DB_ANON_ROLE": role,
|
||||||
}
|
}
|
||||||
|
|
||||||
with run(env=env) as postgrest:
|
with run(env=env) as postgrest:
|
||||||
|
|
||||||
# make it impossible to load the schema cache
|
# make it impossible to load the schema cache, by setting statement timeout to 1ms
|
||||||
response = postgrest.session.post(
|
set_statement_timeout(metapostgrest, role, 1)
|
||||||
"/rpc/no_schema_cache_for_limited_authenticator"
|
|
||||||
)
|
|
||||||
assert response.status_code == 204
|
|
||||||
|
|
||||||
# force a reconnection so the new role setting is picked up
|
# force a reconnection so the new role setting is picked up
|
||||||
postgrest.process.send_signal(signal.SIGUSR1)
|
postgrest.process.send_signal(signal.SIGUSR1)
|
||||||
|
|||||||
Reference in New Issue
Block a user