Files
openafc_final/rcache/rcache_app.py
2024-03-25 10:11:24 -04:00

203 lines
6.4 KiB
Python

#!/usr/bin/env python3
""" AFC Request cache """
#
# Copyright (C) 2023 Broadcom. All rights reserved. The term "Broadcom"
# refers solely to the Broadcom Inc. corporate affiliate that owns
# the software below. This work is licensed under the OpenAFC Project License,
# a copy of which is included with this software program
#
# pylint: disable=wrong-import-order, global-statement, unnecessary-pass
import fastapi
import logging
import uvicorn
import sys
from typing import Annotated, Optional
from rcache_common import dp, get_module_logger, set_dp_printer
from rcache_models import IfDbExists, RcacheServiceSettings, RcacheUpdateReq, \
RcacheInvalidateReq, RcacheSpatialInvalidateReq, RcacheStatus
from rcache_service import RcacheService
__all__ = ["app"]
LOGGER = get_module_logger()
LOGGER.setLevel(logging.INFO)
# Parameters (passed via environment variables)
settings = RcacheServiceSettings()
# Request cache object (created upon first request)
rcache_service: Optional[RcacheService] = None
def get_service() -> RcacheService:
""" Returns service object (creating it on first call """
global rcache_service
if not settings.enabled:
raise \
fastapi.HTTPException(
fastapi.status.HTTP_503_SERVICE_UNAVAILABLE,
detail="Rcache not enabled (RCACHE_ENABLED environment "
"variable set to False")
if rcache_service is None:
rcache_service = \
RcacheService(
rcache_db_dsn=settings.postgres_dsn,
precompute_quota=settings.precompute_quota,
afc_req_url=settings.afc_req_url,
rulesets_url=settings.rulesets_url,
config_retrieval_url=settings.config_retrieval_url)
return rcache_service
# FastAPI APP
app = fastapi.FastAPI()
@app.on_event("startup")
async def startup() -> None:
""" App startup event handler """
set_dp_printer(fastapi.logger.logger.error)
logging.basicConfig(level=logging.INFO)
if not settings.enabled:
return
service = get_service()
if not service.check_db_server():
LOGGER.error("Can't connect to postgres database server")
sys.exit()
await service.connect_db(
create_if_absent=True,
recreate_db=settings.if_db_exists == IfDbExists.recreate,
recreate_tables=settings.if_db_exists == IfDbExists.clean)
@app.on_event("shutdown")
async def shutdown() -> None:
""" App shutdown event handler """
if not settings.enabled:
return
await get_service().shutdown()
@app.get("/healthcheck")
async def healthcheck(
response: fastapi.Response,
service: RcacheService = fastapi.Depends(get_service)) -> None:
""" Healthcheck """
response.status_code = fastapi.status.HTTP_200_OK if service.healthy() \
else fastapi.status.HTTP_500_INTERNAL_SERVER_ERROR
@app.post("/invalidate")
async def invalidate(
invalidate_req: RcacheInvalidateReq,
service: RcacheService = fastapi.Depends(get_service)) -> None:
""" Invalidate request handler """
service.invalidate(invalidate_req)
@app.post("/spatial_invalidate")
async def spatial_invalidate(
spatial_invalidate_req: RcacheSpatialInvalidateReq,
service: RcacheService = fastapi.Depends(get_service)) -> None:
""" Spatial invalidate request handler """
service.invalidate(spatial_invalidate_req)
@app.post("/update")
async def update(
update_req: RcacheUpdateReq,
service: RcacheService = fastapi.Depends(get_service)) -> None:
""" Cache update request handler """
service.update(update_req)
@app.post("/invalidation_state/{enabled}")
async def set_invalidation_state(
enabled: Annotated[
bool,
fastapi.Path(
title="true/false to enable/disable invalidation")],
service: RcacheService = fastapi.Depends(get_service)) -> None:
""" Enable/disable invalidation """
await service.set_invalidation_enabled(enabled)
@app.get("/invalidation_state")
async def get_invalidation_state(
service: RcacheService = fastapi.Depends(get_service)) -> bool:
""" Return invalidation enabled state """
return await service.get_invalidation_enabled()
@app.post("/precomputation_state/{enabled}")
async def set_precomputation_state(
enabled: Annotated[
bool,
fastapi.Path(
title="true/false to enable/disable precomputation")],
service: RcacheService = fastapi.Depends(get_service)) -> None:
""" Enable/disable precomputation """
await service.set_precomputation_enabled(enabled)
@app.get("/precomputation_state")
async def get_precomputation_state(
service: RcacheService = fastapi.Depends(get_service)) -> bool:
""" Return precomputation enabled state """
return await service.get_precomputation_enabled()
@app.post("/update_state/{enabled}")
async def setupdate_state(
enabled: Annotated[
bool,
fastapi.Path(
title="true/false to enable/disable update")],
service: RcacheService = fastapi.Depends(get_service)) -> None:
""" Enable/disable update """
await service.set_update_enabled(enabled)
@app.get("/update_state")
async def get_precomputation_state(
service: RcacheService = fastapi.Depends(get_service)) -> bool:
""" Return update enabled state """
return await service.get_update_enabled()
@app.get("/status")
async def get_service_status(
service: RcacheService = fastapi.Depends(get_service)) \
-> RcacheStatus:
""" Get Rcache service status information """
return await service.get_status()
@app.get("/precomputation_quota")
async def get_precompute_quota(
service: RcacheService = fastapi.Depends(get_service)) -> int:
""" Returns current precomputation quota (maximum number of simultaneously
running precomputations) """
return service.precompute_quota
@app.post("/precomputation_quota/{quota}")
async def set_precompute_quota(
quota: Annotated[
int,
fastapi.Path(
title="Maximum number of simultaneous precomputations",
ge=0)],
service: RcacheService = fastapi.Depends(get_service)) -> None:
""" Sets new precomputation quota (maximum number of simultaneously running
precomputations) """
service.precompute_quota = quota
if __name__ == "__main__":
# Autonomous startup
uvicorn.run(app, host="0.0.0.0", port=settings.port, log_level="info")