Skip to content

Commit e4f4f9f

Browse files
Merge pull request #2162 from VWS-Python/healthcheck-docker
Use a Docker healthcheck for the Flask servers
2 parents 5ea417c + ffd9469 commit e4f4f9f

File tree

4 files changed

+48
-18
lines changed

4 files changed

+48
-18
lines changed

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,9 @@ cumulative_timing = false
337337
[tool.coverage.run]
338338

339339
branch = true
340+
omit = [
341+
"src/mock_vws/_flask_server/healthcheck.py",
342+
]
340343

341344
[tool.coverage.report]
342345

src/mock_vws/_flask_server/Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ RUN pip install --no-cache-dir uv==0.1.44 && \
2020
uv pip install --no-cache-dir --upgrade --editable .
2121
EXPOSE 5000
2222
ENTRYPOINT ["python"]
23+
HEALTHCHECK --interval=1s --timeout=10s --start-period=5s --retries=3 CMD ["python", "/app/src/mock_vws/_flask_server/healthcheck.py"]
2324

2425
FROM base as vws
2526
ENV VWS_HOST=0.0.0.0
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
"""
2+
Health check for the Flask server.
3+
"""
4+
5+
import http.client
6+
import socket
7+
import sys
8+
from http import HTTPStatus
9+
10+
11+
def flask_app_healthy(port: int) -> bool:
12+
"""
13+
Check if the Flask app is healthy.
14+
"""
15+
conn = http.client.HTTPConnection("localhost", port)
16+
try:
17+
conn.request("GET", "/some-random-endpoint")
18+
response = conn.getresponse()
19+
except (TimeoutError, http.client.HTTPException, socket.gaierror):
20+
return False
21+
finally:
22+
conn.close()
23+
24+
return response.status in {
25+
HTTPStatus.NOT_FOUND,
26+
HTTPStatus.UNAUTHORIZED,
27+
HTTPStatus.FORBIDDEN,
28+
}
29+
30+
31+
if __name__ == "__main__":
32+
sys.exit(int(not flask_app_healthy(port=5000)))

tests/mock_vws/test_docker.py

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,11 @@
2323
import io
2424
from collections.abc import Iterator
2525

26+
from docker.models.containers import Container
2627
from docker.models.images import Image
2728
from docker.models.networks import Network
2829

2930

30-
# We do not cover this function because hitting particular branches depends on
31-
# timing.
3231
@retry(
3332
wait=wait_fixed(wait=0.5),
3433
stop=stop_after_delay(max_delay=20),
@@ -37,21 +36,18 @@
3736
),
3837
reraise=True,
3938
)
40-
def wait_for_flask_app_to_start(base_url: str) -> None: # pragma: no cover
39+
def wait_for_health_check(container: Container) -> None:
4140
"""
42-
Wait for a server to start.
43-
44-
Args:
45-
base_url: The base URL of the Flask app to wait for.
41+
Wait for a container to pass its health check.
4642
"""
47-
url = f"{base_url}/{uuid.uuid4().hex}"
48-
response = requests.get(url=url, timeout=30)
49-
if response.status_code not in {
50-
HTTPStatus.NOT_FOUND,
51-
HTTPStatus.UNAUTHORIZED,
52-
HTTPStatus.FORBIDDEN,
53-
}:
54-
error_message = f"Could not connect to {url}"
43+
container.reload()
44+
health_status = container.attrs["State"]["Health"]["Status"]
45+
# In theory this might not be hit by coverage.
46+
# Let's keep it required by coverage for now.
47+
if health_status != "healthy":
48+
error_message = (
49+
f"Container {container.name} is not healthy: {health_status}"
50+
)
5551
raise ValueError(error_message)
5652

5753

@@ -186,6 +182,7 @@ def test_build_and_run(
186182
)
187183

188184
for container in (target_manager_container, vws_container, vwq_container):
185+
wait_for_health_check(container=container)
189186
container.reload()
190187

191188
target_manager_port_attrs = target_manager_container.attrs[
@@ -213,9 +210,6 @@ def test_build_and_run(
213210
f"http://{target_manager_host_ip}:{target_manager_host_port}"
214211
)
215212

216-
for base_url in (base_vws_url, base_vwq_url, base_target_manager_url):
217-
wait_for_flask_app_to_start(base_url=base_url)
218-
219213
response = requests.post(
220214
url=f"{base_target_manager_url}/databases",
221215
json=database.to_dict(),

0 commit comments

Comments
 (0)