Skip to content

Commit 98e566f

Browse files
authored
Merge pull request #52 from kiwix/health-check
Health check
2 parents 42e69e8 + 63a3451 commit 98e566f

File tree

8 files changed

+80
-19
lines changed

8 files changed

+80
-19
lines changed

backend/src/mirrors_qa_backend/main.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from fastapi import FastAPI
44

55
from mirrors_qa_backend.db import initialize_mirrors, upgrade_db_schema
6-
from mirrors_qa_backend.routes import auth, tests, worker
6+
from mirrors_qa_backend.routes import auth, health, tests, worker
77

88

99
@asynccontextmanager
@@ -19,6 +19,7 @@ def create_app(*, debug: bool = True):
1919
app.include_router(router=tests.router)
2020
app.include_router(router=auth.router)
2121
app.include_router(router=worker.router)
22+
app.include_router(router=health.router)
2223

2324
return app
2425

backend/src/mirrors_qa_backend/routes/auth.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@
33
import datetime
44
from typing import Annotated
55

6-
from fastapi import APIRouter, Header
6+
from fastapi import APIRouter, Depends, Header
7+
from sqlalchemy.orm import Session
78

89
from mirrors_qa_backend import logger
910
from mirrors_qa_backend.cryptography import verify_signed_message
11+
from mirrors_qa_backend.db import gen_dbsession
1012
from mirrors_qa_backend.db.exceptions import RecordDoesNotExistError
1113
from mirrors_qa_backend.db.worker import get_worker
1214
from mirrors_qa_backend.exceptions import PEMPublicKeyLoadError
13-
from mirrors_qa_backend.routes.dependencies import DbSession
1415
from mirrors_qa_backend.routes.http_errors import (
1516
BadRequestError,
1617
ForbiddenError,
@@ -25,7 +26,7 @@
2526

2627
@router.post("/authenticate")
2728
def authenticate_worker(
28-
session: DbSession,
29+
session: Annotated[Session, Depends(gen_dbsession)],
2930
x_sshauth_message: Annotated[
3031
str,
3132
Header(description="message (format): worker_id:timestamp (UTC ISO)"),

backend/src/mirrors_qa_backend/routes/dependencies.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,12 @@
1616
from mirrors_qa_backend.routes.http_errors import NotFoundError, UnauthorizedError
1717
from mirrors_qa_backend.settings.api import APISettings
1818

19-
DbSession = Annotated[Session, Depends(gen_dbsession)]
20-
2119
security = HTTPBearer(description="Access Token")
22-
AuthorizationCredentials = Annotated[HTTPAuthorizationCredentials, Depends(security)]
2320

2421

2522
def get_current_worker(
26-
session: DbSession,
27-
authorization: AuthorizationCredentials,
23+
session: Annotated[Session, Depends(gen_dbsession)],
24+
authorization: Annotated[HTTPAuthorizationCredentials, Depends(security)],
2825
) -> models.Worker:
2926
token = authorization.credentials
3027
try:
@@ -51,7 +48,10 @@ def get_current_worker(
5148
CurrentWorker = Annotated[models.Worker, Depends(get_current_worker)]
5249

5350

54-
def get_test(session: DbSession, test_id: Annotated[UUID4, Path()]) -> models.Test:
51+
def get_test(
52+
session: Annotated[Session, Depends(gen_dbsession)],
53+
test_id: Annotated[UUID4, Path()],
54+
) -> models.Test:
5555
"""Fetches the test specified in the request."""
5656
try:
5757
test = db_get_test(session, test_id)
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import datetime
2+
from typing import Annotated
3+
4+
from fastapi import APIRouter, Depends
5+
from fastapi import status as status_codes
6+
from sqlalchemy import select
7+
from sqlalchemy.orm import Session
8+
9+
from mirrors_qa_backend.db import count_from_stmt, gen_dbsession
10+
from mirrors_qa_backend.db.models import Test
11+
from mirrors_qa_backend.enums import StatusEnum
12+
from mirrors_qa_backend.schemas import HealthStatus
13+
from mirrors_qa_backend.settings.api import APISettings
14+
15+
router = APIRouter(prefix="/health-check", tags=["health-check"])
16+
17+
18+
@router.get(
19+
"",
20+
status_code=status_codes.HTTP_200_OK,
21+
responses={
22+
status_codes.HTTP_200_OK: {
23+
"description": "Status of monitored parts of mirrors-qa"
24+
}
25+
},
26+
)
27+
def heatlh_status(session: Annotated[Session, Depends(gen_dbsession)]) -> HealthStatus:
28+
test_received_after = datetime.datetime.now() - datetime.timedelta(
29+
seconds=APISettings.UNHEALTHY_NO_TESTS_DURATION_SECONDS
30+
)
31+
32+
nb_recent_tests_received = count_from_stmt(
33+
session,
34+
select(Test).where(
35+
Test.status == StatusEnum.SUCCEEDED,
36+
(Test.started_on >= test_received_after),
37+
),
38+
)
39+
40+
return HealthStatus(receiving_tests=nb_recent_tests_received > 0)

backend/src/mirrors_qa_backend/routes/tests.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,16 @@
22

33
from fastapi import APIRouter, Depends, Query
44
from fastapi import status as status_codes
5+
from sqlalchemy.orm import Session
56

67
from mirrors_qa_backend import schemas
8+
from mirrors_qa_backend.db import gen_dbsession
79
from mirrors_qa_backend.db.tests import list_tests as db_list_tests
810
from mirrors_qa_backend.db.tests import update_test as update_test_model
911
from mirrors_qa_backend.db.worker import update_worker_last_seen
1012
from mirrors_qa_backend.enums import SortDirectionEnum, StatusEnum, TestSortColumnEnum
1113
from mirrors_qa_backend.routes.dependencies import (
1214
CurrentWorker,
13-
DbSession,
1415
RetrievedTest,
1516
verify_worker_owns_test,
1617
)
@@ -29,7 +30,7 @@
2930
},
3031
)
3132
def list_tests(
32-
session: DbSession,
33+
session: Annotated[Session, Depends(gen_dbsession)],
3334
worker_id: Annotated[str | None, Query()] = None,
3435
country_code: Annotated[str | None, Query(min_length=2, max_length=2)] = None,
3536
status: Annotated[list[StatusEnum] | None, Query()] = None,
@@ -81,7 +82,7 @@ def get_test(test: RetrievedTest) -> Test:
8182
dependencies=[Depends(verify_worker_owns_test)],
8283
)
8384
def update_test(
84-
session: DbSession,
85+
session: Annotated[Session, Depends(gen_dbsession)],
8586
current_worker: CurrentWorker,
8687
test: RetrievedTest,
8788
update: schemas.UpdateTestModel,

backend/src/mirrors_qa_backend/routes/worker.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
1+
from typing import Annotated
2+
13
import pycountry
2-
from fastapi import APIRouter
4+
from fastapi import APIRouter, Depends
35
from fastapi import status as status_codes
6+
from sqlalchemy.orm import Session
47

8+
from mirrors_qa_backend.db import gen_dbsession
59
from mirrors_qa_backend.db.country import update_countries as update_db_countries
610
from mirrors_qa_backend.db.exceptions import RecordDoesNotExistError
711
from mirrors_qa_backend.db.worker import get_worker as get_db_worker
812
from mirrors_qa_backend.db.worker import update_worker as update_db_worker
9-
from mirrors_qa_backend.routes.dependencies import CurrentWorker, DbSession
13+
from mirrors_qa_backend.routes.dependencies import CurrentWorker
1014
from mirrors_qa_backend.routes.http_errors import (
1115
BadRequestError,
1216
NotFoundError,
@@ -27,7 +31,9 @@
2731
}
2832
},
2933
)
30-
def list_countries(session: DbSession, worker_id: str) -> WorkerCountries:
34+
def list_countries(
35+
session: Annotated[Session, Depends(gen_dbsession)], worker_id: str
36+
) -> WorkerCountries:
3137
try:
3238
worker = get_db_worker(session, worker_id)
3339
except RecordDoesNotExistError as exc:
@@ -48,7 +54,7 @@ def list_countries(session: DbSession, worker_id: str) -> WorkerCountries:
4854
},
4955
)
5056
def update_countries(
51-
session: DbSession,
57+
session: Annotated[Session, Depends(gen_dbsession)],
5258
worker_id: str,
5359
current_worker: CurrentWorker,
5460
data: UpdateWorkerCountries,

backend/src/mirrors_qa_backend/schemas.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,3 +110,7 @@ class JWTClaims(BaseModel):
110110
exp: datetime.datetime
111111
iat: datetime.datetime
112112
subject: str
113+
114+
115+
class HealthStatus(BaseModel):
116+
receiving_tests: bool

backend/src/mirrors_qa_backend/settings/api.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,16 @@ class APISettings(Settings):
88

99
JWT_SECRET: str = getenv("JWT_SECRET", mandatory=True)
1010
# number of seconds before a message expire
11-
MESSAGE_VALIDITY_SECONDS = parse_timespan(
11+
MESSAGE_VALIDITY_SECONDS: float = parse_timespan(
1212
getenv("MESSAGE_VALIDITY_DURATION", default="1m")
1313
)
1414
# number of hours before access tokens expire
15-
TOKEN_EXPIRY_SECONDS = parse_timespan(getenv("TOKEN_EXPIRY_DURATION", default="6h"))
15+
TOKEN_EXPIRY_SECONDS: float = parse_timespan(
16+
getenv("TOKEN_EXPIRY_DURATION", default="6h")
17+
)
18+
19+
# number of seconds after which to consider that not having received
20+
# successful Test is an issue
21+
UNHEALTHY_NO_TESTS_DURATION_SECONDS: float = parse_timespan(
22+
getenv("UNHEALTHY_NO_TESTS_DURATION_SECONDS", default="6h")
23+
)

0 commit comments

Comments
 (0)