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:
Robert Vollmert
2022-07-29 21:46:57 +02:00
committed by Robert
parent 8911afd079
commit d556cea8ce
2 changed files with 111 additions and 13 deletions
+10 -5
View File
@@ -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
View File
@@ -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)