Skip to content

Commit 70327cf

Browse files
committed
test: negative pgrst_db_pool_available in metrics
Proves the failure on #4622. This doesn't require additional test infra, only nginx. Taking advantage of the `stream {}` context which is also compatible with unix socket besides TCP.
1 parent a971320 commit 70327cf

5 files changed

Lines changed: 90 additions & 1 deletion

File tree

nix/tools/tests.nix

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
, hpc-codecov
1010
, jq
1111
, lib
12+
, nginx
1213
, postgrest
1314
, python3
1415
, runtimeShell
@@ -93,6 +94,7 @@ let
9394
args = [ "ARG_LEFTOVERS([pytest arguments])" ];
9495
workingDir = "/";
9596
withEnv = postgrest.env;
97+
withPath = [ nginx ];
9698
}
9799
''
98100
${cabal-install}/bin/cabal v2-build ${devCabalOptions} exe:postgrest
@@ -155,6 +157,7 @@ let
155157
redirectTixFiles = false;
156158
withEnv = postgrest.env;
157159
withTmpDir = true;
160+
withPath = [ nginx ];
158161
}
159162
(
160163
# required for `hpc markup` in CI; glibcLocales is not available e.g. on Darwin

test/io/config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
FIXTURES = yaml.load(
1111
(BASEDIR / "fixtures/fixtures.yaml").read_text(), Loader=yaml.Loader
1212
)
13+
NGINX_BIN = shutil.which("nginx")
1314
POSTGREST_BIN = shutil.which("postgrest")
1415
SECRET = "reallyreallyreallyreallyverysafe"
1516

test/io/nginx/nginx.conf

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# the PG* variables are replaced by preprocessing, not done by nginx itself
2+
daemon off;
3+
pid ./nginx.pid;
4+
5+
events {}
6+
7+
stream {
8+
server {
9+
listen unix:$PGPROXYHOST/.s.PGSQL.5432;
10+
proxy_timeout $PGPROXY_TIMEOUT;
11+
proxy_pass unix:$PGHOST/.s.PGSQL.5432;
12+
}
13+
}

test/io/postgrest.py

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@
88
import subprocess
99
import tempfile
1010
import time
11+
import string
1112
import urllib.parse
1213

1314
import requests
1415
import requests_unixsocket
1516

16-
from config import POSTGREST_BIN, hpctixfile
17+
from config import POSTGREST_BIN, NGINX_BIN, hpctixfile
1718

1819

1920
def sleep_until_postgrest_scache_reload():
@@ -170,6 +171,52 @@ def run(
170171
process.wait()
171172

172173

174+
@contextlib.contextmanager
175+
def run_pgproxy(env=None, proxy_timeout="1s"):
176+
"Run nginx as a unix socket proxy for PostgreSQL and expose PGPROXYHOST."
177+
env = dict(os.environ if env is None else env)
178+
179+
with tempfile.TemporaryDirectory() as tmpdir:
180+
# build a <tmpdir>/conf/ so `nginx -p` picks the config automatically
181+
tmpdir = pathlib.Path(tmpdir)
182+
conf_dir = tmpdir / "conf"
183+
conf_dir.mkdir(parents=True)
184+
185+
nginx_env = dict(env)
186+
nginx_env["PGPROXYHOST"] = str(tmpdir)
187+
nginx_env["PGPROXY_TIMEOUT"] = proxy_timeout
188+
189+
source_conf = pathlib.Path("test/io/nginx/nginx.conf")
190+
out_conf = conf_dir / "nginx.conf"
191+
out_conf.write_text(
192+
string.Template(source_conf.read_text()).substitute(nginx_env)
193+
)
194+
195+
process = subprocess.Popen(
196+
[NGINX_BIN, "-p", str(tmpdir), "-e", "stderr"],
197+
stdout=subprocess.PIPE,
198+
stderr=subprocess.PIPE,
199+
text=True,
200+
env=nginx_env,
201+
)
202+
203+
if process.poll() is not None:
204+
(_, stderr_output) = process.communicate(timeout=1)
205+
raise RuntimeError(
206+
f"{NGINX_BIN} exited with {process.returncode}: {stderr_output}"
207+
)
208+
209+
try:
210+
yield str(tmpdir)
211+
finally:
212+
process.terminate()
213+
try:
214+
process.wait(timeout=1)
215+
except subprocess.TimeoutExpired:
216+
process.kill()
217+
process.wait()
218+
219+
173220
def freeport(used_ports=None):
174221
"Find an unused free port on localhost."
175222
while True:

test/io/test_io.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
is_ipv6,
1515
reset_statement_timeout,
1616
run,
17+
run_pgproxy,
1718
set_statement_timeout,
1819
sleep_until_postgrest_config_reload,
1920
sleep_until_postgrest_full_reload,
@@ -2178,3 +2179,27 @@ def test_vary_default_header_set(defaultenv):
21782179
response = postgrest.session.get("/projects")
21792180

21802181
assert response.headers["Vary"] == "Accept, Prefer, Range"
2182+
2183+
2184+
@pytest.mark.xfail(
2185+
reason="pgrst_db_pool_available should not go negative on pg network failures",
2186+
strict=True,
2187+
)
2188+
def test_positive_pool_metric(defaultenv):
2189+
"When a network failure is caused on the pg connection, pgrst_db_pool_available stays positive"
2190+
2191+
with run_pgproxy(defaultenv, proxy_timeout="10ms") as pgproxyhost:
2192+
env = {**defaultenv, "PGHOST": pgproxyhost}
2193+
2194+
with run(env=env, wait_for_readiness=False) as postgrest:
2195+
time.sleep(2)
2196+
2197+
response = postgrest.admin.get("/metrics", timeout=1)
2198+
assert response.status_code == 200
2199+
2200+
metrics = float(
2201+
re.search(
2202+
r"pgrst_db_pool_available (-?\d+(?:\.\d+)?)", response.text
2203+
).group(1)
2204+
)
2205+
assert metrics >= 0

0 commit comments

Comments
 (0)