Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Health check #52

Merged
merged 2 commits into from
Oct 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion backend/src/mirrors_qa_backend/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from fastapi import FastAPI

from mirrors_qa_backend.db import initialize_mirrors, upgrade_db_schema
from mirrors_qa_backend.routes import auth, tests, worker
from mirrors_qa_backend.routes import auth, health, tests, worker


@asynccontextmanager
Expand All @@ -19,6 +19,7 @@ def create_app(*, debug: bool = True):
app.include_router(router=tests.router)
app.include_router(router=auth.router)
app.include_router(router=worker.router)
app.include_router(router=health.router)

return app

Expand Down
7 changes: 4 additions & 3 deletions backend/src/mirrors_qa_backend/routes/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@
import datetime
from typing import Annotated

from fastapi import APIRouter, Header
from fastapi import APIRouter, Depends, Header
from sqlalchemy.orm import Session

from mirrors_qa_backend import logger
from mirrors_qa_backend.cryptography import verify_signed_message
from mirrors_qa_backend.db import gen_dbsession
from mirrors_qa_backend.db.exceptions import RecordDoesNotExistError
from mirrors_qa_backend.db.worker import get_worker
from mirrors_qa_backend.exceptions import PEMPublicKeyLoadError
from mirrors_qa_backend.routes.dependencies import DbSession
from mirrors_qa_backend.routes.http_errors import (
BadRequestError,
ForbiddenError,
Expand All @@ -25,7 +26,7 @@

@router.post("/authenticate")
def authenticate_worker(
session: DbSession,
session: Annotated[Session, Depends(gen_dbsession)],
x_sshauth_message: Annotated[
str,
Header(description="message (format): worker_id:timestamp (UTC ISO)"),
Expand Down
12 changes: 6 additions & 6 deletions backend/src/mirrors_qa_backend/routes/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,12 @@
from mirrors_qa_backend.routes.http_errors import NotFoundError, UnauthorizedError
from mirrors_qa_backend.settings.api import APISettings

DbSession = Annotated[Session, Depends(gen_dbsession)]

security = HTTPBearer(description="Access Token")
AuthorizationCredentials = Annotated[HTTPAuthorizationCredentials, Depends(security)]


def get_current_worker(
session: DbSession,
authorization: AuthorizationCredentials,
session: Annotated[Session, Depends(gen_dbsession)],
authorization: Annotated[HTTPAuthorizationCredentials, Depends(security)],
) -> models.Worker:
token = authorization.credentials
try:
Expand All @@ -51,7 +48,10 @@ def get_current_worker(
CurrentWorker = Annotated[models.Worker, Depends(get_current_worker)]


def get_test(session: DbSession, test_id: Annotated[UUID4, Path()]) -> models.Test:
def get_test(
session: Annotated[Session, Depends(gen_dbsession)],
test_id: Annotated[UUID4, Path()],
) -> models.Test:
"""Fetches the test specified in the request."""
try:
test = db_get_test(session, test_id)
Expand Down
40 changes: 40 additions & 0 deletions backend/src/mirrors_qa_backend/routes/health.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import datetime
from typing import Annotated

from fastapi import APIRouter, Depends
from fastapi import status as status_codes
from sqlalchemy import select
from sqlalchemy.orm import Session

from mirrors_qa_backend.db import count_from_stmt, gen_dbsession
from mirrors_qa_backend.db.models import Test
from mirrors_qa_backend.enums import StatusEnum
from mirrors_qa_backend.schemas import HealthStatus
from mirrors_qa_backend.settings.api import APISettings

router = APIRouter(prefix="/health-check", tags=["health-check"])


@router.get(
"",
status_code=status_codes.HTTP_200_OK,
responses={
status_codes.HTTP_200_OK: {
"description": "Status of monitored parts of mirrors-qa"
}
},
)
def heatlh_status(session: Annotated[Session, Depends(gen_dbsession)]) -> HealthStatus:
test_received_after = datetime.datetime.now() - datetime.timedelta(
seconds=APISettings.UNHEALTHY_NO_TESTS_DURATION_SECONDS
)

nb_recent_tests_received = count_from_stmt(
session,
select(Test).where(
Test.status == StatusEnum.SUCCEEDED,
(Test.started_on >= test_received_after),
),
)

return HealthStatus(receiving_tests=nb_recent_tests_received > 0)
7 changes: 4 additions & 3 deletions backend/src/mirrors_qa_backend/routes/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@

from fastapi import APIRouter, Depends, Query
from fastapi import status as status_codes
from sqlalchemy.orm import Session

from mirrors_qa_backend import schemas
from mirrors_qa_backend.db import gen_dbsession
from mirrors_qa_backend.db.tests import list_tests as db_list_tests
from mirrors_qa_backend.db.tests import update_test as update_test_model
from mirrors_qa_backend.db.worker import update_worker_last_seen
from mirrors_qa_backend.enums import SortDirectionEnum, StatusEnum, TestSortColumnEnum
from mirrors_qa_backend.routes.dependencies import (
CurrentWorker,
DbSession,
RetrievedTest,
verify_worker_owns_test,
)
Expand All @@ -29,7 +30,7 @@
},
)
def list_tests(
session: DbSession,
session: Annotated[Session, Depends(gen_dbsession)],
worker_id: Annotated[str | None, Query()] = None,
country_code: Annotated[str | None, Query(min_length=2, max_length=2)] = None,
status: Annotated[list[StatusEnum] | None, Query()] = None,
Expand Down Expand Up @@ -81,7 +82,7 @@ def get_test(test: RetrievedTest) -> Test:
dependencies=[Depends(verify_worker_owns_test)],
)
def update_test(
session: DbSession,
session: Annotated[Session, Depends(gen_dbsession)],
current_worker: CurrentWorker,
test: RetrievedTest,
update: schemas.UpdateTestModel,
Expand Down
14 changes: 10 additions & 4 deletions backend/src/mirrors_qa_backend/routes/worker.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
from typing import Annotated

import pycountry
from fastapi import APIRouter
from fastapi import APIRouter, Depends
from fastapi import status as status_codes
from sqlalchemy.orm import Session

from mirrors_qa_backend.db import gen_dbsession
from mirrors_qa_backend.db.country import update_countries as update_db_countries
from mirrors_qa_backend.db.exceptions import RecordDoesNotExistError
from mirrors_qa_backend.db.worker import get_worker as get_db_worker
from mirrors_qa_backend.db.worker import update_worker as update_db_worker
from mirrors_qa_backend.routes.dependencies import CurrentWorker, DbSession
from mirrors_qa_backend.routes.dependencies import CurrentWorker
from mirrors_qa_backend.routes.http_errors import (
BadRequestError,
NotFoundError,
Expand All @@ -27,7 +31,9 @@
}
},
)
def list_countries(session: DbSession, worker_id: str) -> WorkerCountries:
def list_countries(
session: Annotated[Session, Depends(gen_dbsession)], worker_id: str
) -> WorkerCountries:
try:
worker = get_db_worker(session, worker_id)
except RecordDoesNotExistError as exc:
Expand All @@ -48,7 +54,7 @@ def list_countries(session: DbSession, worker_id: str) -> WorkerCountries:
},
)
def update_countries(
session: DbSession,
session: Annotated[Session, Depends(gen_dbsession)],
worker_id: str,
current_worker: CurrentWorker,
data: UpdateWorkerCountries,
Expand Down
4 changes: 4 additions & 0 deletions backend/src/mirrors_qa_backend/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,7 @@ class JWTClaims(BaseModel):
exp: datetime.datetime
iat: datetime.datetime
subject: str


class HealthStatus(BaseModel):
receiving_tests: bool
12 changes: 10 additions & 2 deletions backend/src/mirrors_qa_backend/settings/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,16 @@ class APISettings(Settings):

JWT_SECRET: str = getenv("JWT_SECRET", mandatory=True)
# number of seconds before a message expire
MESSAGE_VALIDITY_SECONDS = parse_timespan(
MESSAGE_VALIDITY_SECONDS: float = parse_timespan(
getenv("MESSAGE_VALIDITY_DURATION", default="1m")
)
# number of hours before access tokens expire
TOKEN_EXPIRY_SECONDS = parse_timespan(getenv("TOKEN_EXPIRY_DURATION", default="6h"))
TOKEN_EXPIRY_SECONDS: float = parse_timespan(
getenv("TOKEN_EXPIRY_DURATION", default="6h")
)

# number of seconds after which to consider that not having received
# successful Test is an issue
UNHEALTHY_NO_TESTS_DURATION_SECONDS: float = parse_timespan(
getenv("UNHEALTHY_NO_TESTS_DURATION_SECONDS", default="6h")
)
Loading