From f58a2f7cc60c67f3d09b3c4a2040170af45036c5 Mon Sep 17 00:00:00 2001 From: John Jewell Date: Wed, 17 Apr 2024 11:33:59 -0400 Subject: [PATCH 1/2] Add check status endpoint for server --- florist/api/routes/server/status.py | 44 +++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 florist/api/routes/server/status.py diff --git a/florist/api/routes/server/status.py b/florist/api/routes/server/status.py new file mode 100644 index 00000000..bbababf3 --- /dev/null +++ b/florist/api/routes/server/status.py @@ -0,0 +1,44 @@ +import json +import logging + +import redis + +from fastapi import APIRouter +from fastapi.responses import JSONResponse + +router = APIRouter() + +LOGGER = logging.getLogger("uvicorn.error") + +@router.get( + path="/", + response_description="Check status of the server", +) +def check_status(server_uuid: str, redis_host: str, redis_port: str) -> JSONResponse: + """ + Retrieve value at key server_uuid in redis if it exists. + + :param server_uuid: (str) the uuid of the server to fetch from redis. + :param redis_host: (str) the host name for the Redis instance for metrics reporting. + :param redis_port: (str) the port for the Redis instance for metrics reporting. + + :return: (JSONResponse) If successful, returns 200 with JSON containing the val at `server_uuid`. + If not successful, returns the appropriate error code with a JSON with the format below: + {"error": } + """ + try: + redis_connection = redis.Redis(host=redis_host, port=redis_port) + + result = redis_connection.get(server_uuid) + + if result is not None: + assert isinstance(result, bytes) + return JSONResponse(json.loads(result)) + + return JSONResponse({"error": f"Server {server_uuid} Not Found"}, status_code=404) + + except Exception as ex: + LOGGER.exception(ex) + return JSONResponse({"error": str(ex)}, status_code=500) + + From c81901c1d9a13151ecd8c4e815bce12bddd5d519 Mon Sep 17 00:00:00 2001 From: John Jewell Date: Wed, 17 Apr 2024 12:23:49 -0400 Subject: [PATCH 2/2] Add tests for server check status. Add path parameter for server and client check status. Add server check status to server routes --- florist/api/client.py | 2 +- florist/api/routes/server/status.py | 8 +-- florist/api/server.py | 2 + .../unit/api/routes/server/test_status.py | 50 +++++++++++++++++++ 4 files changed, 57 insertions(+), 5 deletions(-) create mode 100644 florist/tests/unit/api/routes/server/test_status.py diff --git a/florist/api/client.py b/florist/api/client.py index 9fc2fa6f..27529e05 100644 --- a/florist/api/client.py +++ b/florist/api/client.py @@ -75,7 +75,7 @@ def start(server_address: str, client: str, data_path: str, redis_host: str, red return JSONResponse({"error": str(ex)}, status_code=500) -@app.get("/api/client/check_status") +@app.get("/api/client/check_status/{client_uuid}") def check_status(client_uuid: str, redis_host: str, redis_port: str) -> JSONResponse: """ Retrieve value at key client_uuid in redis if it exists. diff --git a/florist/api/routes/server/status.py b/florist/api/routes/server/status.py index bbababf3..4d13882f 100644 --- a/florist/api/routes/server/status.py +++ b/florist/api/routes/server/status.py @@ -1,17 +1,19 @@ +"""FastAPI routes for checking server status.""" import json import logging import redis - from fastapi import APIRouter from fastapi.responses import JSONResponse + router = APIRouter() LOGGER = logging.getLogger("uvicorn.error") + @router.get( - path="/", + path="/{server_uuid}", response_description="Check status of the server", ) def check_status(server_uuid: str, redis_host: str, redis_port: str) -> JSONResponse: @@ -40,5 +42,3 @@ def check_status(server_uuid: str, redis_host: str, redis_port: str) -> JSONResp except Exception as ex: LOGGER.exception(ex) return JSONResponse({"error": str(ex)}, status_code=500) - - diff --git a/florist/api/server.py b/florist/api/server.py index f962c805..040e3eb1 100644 --- a/florist/api/server.py +++ b/florist/api/server.py @@ -6,6 +6,7 @@ from motor.motor_asyncio import AsyncIOMotorClient from florist.api.routes.server.job import router as job_router +from florist.api.routes.server.status import router as status_router from florist.api.routes.server.training import router as training_router @@ -29,3 +30,4 @@ async def lifespan(app: FastAPI) -> AsyncGenerator[Any, Any]: app = FastAPI(lifespan=lifespan) app.include_router(training_router, tags=["training"], prefix="/api/server/training") app.include_router(job_router, tags=["job"], prefix="/api/server/job") +app.include_router(status_router, tags=["status"], prefix="/api/server/check_status") diff --git a/florist/tests/unit/api/routes/server/test_status.py b/florist/tests/unit/api/routes/server/test_status.py new file mode 100644 index 00000000..c50befc6 --- /dev/null +++ b/florist/tests/unit/api/routes/server/test_status.py @@ -0,0 +1,50 @@ +import json +from unittest.mock import Mock, patch + +from florist.api.routes.server.status import check_status + + +@patch("florist.api.routes.server.status.redis") +def test_check_status(mock_redis: Mock) -> None: + mock_redis_connection = Mock() + mock_redis_connection.get.return_value = b"{\"info\": \"test\"}" + + test_uuid = "test_uuid" + test_redis_host = "localhost" + test_redis_port = "testport" + + mock_redis.Redis.return_value = mock_redis_connection + + response = check_status(test_uuid, test_redis_host, test_redis_port) + + mock_redis.Redis.assert_called_with(host=test_redis_host, port=test_redis_port) + assert json.loads(response.body.decode()) == {"info": "test"} + +@patch("florist.api.routes.server.status.redis") +def test_check_status_not_found(mock_redis: Mock) -> None: + mock_redis_connection = Mock() + mock_redis_connection.get.return_value = None + + test_uuid = "test_uuid" + test_redis_host = "localhost" + test_redis_port = "testport" + + mock_redis.Redis.return_value = mock_redis_connection + + response = check_status(test_uuid, test_redis_host, test_redis_port) + + mock_redis.Redis.assert_called_with(host=test_redis_host, port=test_redis_port) + assert response.status_code == 404 + assert json.loads(response.body.decode()) == {"error": f"Server {test_uuid} Not Found"} + +@patch("florist.api.routes.server.status.redis.Redis", side_effect=Exception("test exception")) +def test_check_status_fail_exception(mock_redis: Mock) -> None: + + test_uuid = "test_uuid" + test_redis_host = "localhost" + test_redis_port = "testport" + + response = check_status(test_uuid, test_redis_host, test_redis_port) + + assert response.status_code == 500 + assert json.loads(response.body.decode()) == {"error": "test exception"}