Skip to content

Commit

Permalink
api autodoc
Browse files Browse the repository at this point in the history
  • Loading branch information
witlox committed Dec 11, 2024
1 parent 67286e3 commit 5cc0876
Show file tree
Hide file tree
Showing 7 changed files with 313 additions and 75 deletions.
45 changes: 14 additions & 31 deletions horao/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter # type: ignore
from opentelemetry.exporter.otlp.proto.http.metric_exporter import OTLPMetricExporter # type: ignore

from apiman.starlette import Apiman # type: ignore
from opentelemetry.sdk.resources import SERVICE_NAME, Resource # type: ignore
from opentelemetry.sdk.trace import TracerProvider # type: ignore
from opentelemetry.sdk.trace.export import BatchSpanProcessor # type: ignore
Expand Down Expand Up @@ -94,34 +95,6 @@
metrics.set_meter_provider(meter_provider)


schemas = SchemaGenerator(
{"openapi": "3.0.0", "info": {"title": "HORAO API", "version": "1.0"}}
)


def openapi_schema(request):
return schemas.OpenAPIResponse(request=request)


async def docs(request):
html = f"""
<!DOCTYPE html>
<html>
<head>
<title>HORAO - Redoc</title>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
</head>
<body>
<redoc spec-url="{request.url.scheme}://{request.url.netloc}/openapi.json"></redoc>
<script src="https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js"> </script>
</body>
</html>
"""
return HTMLResponse(html)


def init(authorization: Optional[AuthenticationBackend] = None) -> Starlette:
"""
Initialize the API
Expand Down Expand Up @@ -150,10 +123,12 @@ def init(authorization: Optional[AuthenticationBackend] = None) -> Starlette:
endpoint=horao.api.user_actions.get_reservations,
methods=["GET"],
),
Route("/openapi.json", endpoint=openapi_schema, include_in_schema=False),
Route(
"/reservation",
endpoint=horao.api.user_actions.create_reservation,
methods=["POST"],
),
]
if os.getenv("UI", "False") == "True":
routes.append(Route("/docs", endpoint=docs, methods=["GET"]))
middleware = [
Middleware(CORSMiddleware, allow_origins=[cors]),
]
Expand All @@ -169,6 +144,14 @@ def init(authorization: Optional[AuthenticationBackend] = None) -> Starlette:
middleware=middleware,
debug=False if os.getenv("DEBUG", "False") == "False" else True,
)
if os.getenv("UI", "False") == "True":
apiman = Apiman(
title="Horao",
specification_url="/spec/",
redoc_url="/docs/",
template="openapi/openapi.yml",
)
apiman.init_app(app)
if os.getenv("TELEMETRY", "ON") == "OFF":
logger.warning("Telemetry is turned off")
else:
Expand Down
26 changes: 18 additions & 8 deletions horao/api/authenticate.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,16 @@

async def login(request: Request):
"""
responses:
401:
description: Not authorized
302:
description: Redirect after successful login
login
---
post:
summary: login
description: Login with OpenID Connect
responses:
"401":
description: Not authorized
"302":
description: Redirect after successful login
"""
redirect_uri = request.url_for("auth")
oauth_role_uri = os.getenv("OAUTH_ROLE_URI", "role")
Expand Down Expand Up @@ -42,9 +47,14 @@ async def login(request: Request):
@requires("authenticated")
async def logout(request: Request):
"""
responses:
302:
description: Redirect after successful logout
logout
---
post:
summary: Logout
description: Logout
responses:
"302":
description: Redirect after successful logout
"""
request.session.pop("user", None)
return RedirectResponse(url="/")
64 changes: 47 additions & 17 deletions horao/api/synchronization.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,50 @@
@permission_required(Namespace.Peer, Permission.Write)
async def synchronize(request: Request) -> JSONResponse:
"""
responses:
200:
description: synchronize logical infrastructures.
examples:
{ "LogicalInfrastructure": {
"type": "LogicalInfrastructure",
"infrastructure": {},
"constraints": {},
"claims": {},
}}
400:
description: Error parsing request
403:
description: Unauthorized
500:
description: Error synchronizing
synchronize
---
post:
summary: synchronize
description: Synchronize infrastructure, claims and constraints among HORAO instances
tags:
- system
requestBody:
description: infrastructure, claims and constraints
required: true
content:
application/json:
schema:
type: object
properties:
infrastructure:
type: object
additionalProperties:
type: array
claims:
type: object
additionalProperties:
type: array
constraints:
type: object
additionalProperties:
type: array
example:
infrastructure:
"dc1": []
"dc2": []
constraints:
"constraint1": []
claims:
"claim1": []
responses:
"200":
description: Synchronization successful
"400":
description: Error parsing request
"403":
description: Unauthorized
"500":
description: Error synchronizing
"""
logging.debug(f"Calling Synchronize ({request})")
try:
Expand All @@ -55,4 +83,6 @@ async def synchronize(request: Request) -> JSONResponse:
status_code=500, content={"error": f"Error synchronizing {str(e)}"}
)
return JSONResponse(status_code=500, content={"error": f"Error synchronizing"})
return JSONResponse(status_code=200, content={"status": "is alive"})
return JSONResponse(
status_code=200, content={"message": "Synchronization successful"}
)
144 changes: 126 additions & 18 deletions horao/api/user_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,30 +9,55 @@

from horao.auth.permissions import Namespace, Permission
from horao.auth.validate import permission_required
from horao.persistance import HoraoEncoder, init_session
from horao.logical.scheduler import Scheduler
from horao.persistance import HoraoDecoder, HoraoEncoder, init_session


@requires("authenticated")
@permission_required(Namespace.User, Permission.Read)
async def get_reservations(request: Request) -> JSONResponse:
"""
responses:
200:
description: reservations for current user.
examples: [
{"type": "Reservation",
"name": "reservation",
"start": "start timestamp",
"end": "end timestamp",
"resources": [],
"maximal_resources": [],
"hsn_only": obj.hsn_only,
}
]
403:
description: Unauthorized
500:
description: Error processing request
reservations
---
get:
summary: reservations
description: Get reservations for current user
tags:
- user
responses:
"200":
description: Reservations for current user
content:
application/json:
schema:
type: object
properties:
claims:
type: array
items:
schema:
type: object
properties:
name:
type: string
start:
type: string
end:
type: string
resources:
type: array
items:
type: string
maximal_resources:
type: array
items:
type: string
hsn_only:
type: boolean
"403":
description: Unauthorized
"500":
description: Error processing request
"""
logging.debug(f"Calling Reservations ({request})")
try:
Expand All @@ -54,3 +79,86 @@ async def get_reservations(request: Request) -> JSONResponse:
return JSONResponse(
status_code=500, content={"error": f"Error processing request"}
)


@requires("authenticated")
@permission_required(Namespace.User, Permission.Write)
async def create_reservation(request: Request) -> JSONResponse:
"""
reservation
---
post:
summary: reservation
description: Create a reservation
tags:
- user
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
name:
type: string
start:
type: string
end:
type: string
resources:
type: array
items:
type: string
maximal_resources:
type: array
items:
type: string
hsn_only:
type: boolean
example:
name: "r01"
start: "start timestamp"
end: "end timestamp"
resources: []
maximal_resources: []
hsn_only: False
responses:
"200":
description: Reservation created
"400":
description: Error parsing request
"403":
description: Unauthorized
"500":
description: Error processing reservation
"""
logging.debug(f"Calling Reservations ({request})")
try:
data = await request.json()
claim = json.loads(data, cls=HoraoDecoder)
except Exception as e:
logging.error(f"Error parsing request: {e}")
if os.getenv("DEBUG", "False") == "True":
return JSONResponse(
status_code=400, content={"error": f"Error parsing request {str(e)}"}
)
return JSONResponse(status_code=400, content={"error": "Error parsing request"})
try:
session = init_session()
logical_infrastructure = await session.load_logical_infrastructure()
scheduler = Scheduler(logical_infrastructure)
user = request.user
start = scheduler.schedule(claim, user.tenant)
except Exception as e:
logging.error(f"Error processing request: {e}")
if os.getenv("DEBUG", "False") == "True":
return JSONResponse(
status_code=500, content={"error": f"Error processing request {str(e)}"}
)
return JSONResponse(
status_code=500, content={"error": f"Error processing request"}
)
return JSONResponse(
status_code=200,
content={"reservation_start": json.dumps(start, cls=HoraoEncoder)},
)
5 changes: 5 additions & 0 deletions horao/openapi/openapi.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
openapi: "3.0.0"
info:
title: 'HORAO'
version: '2024.1.1'
description: 'API calls for HORAO'
Loading

0 comments on commit 5cc0876

Please sign in to comment.