1
0
mirror of https://github.com/checktheroads/hyperglass synced 2024-05-11 05:55:08 +00:00
Files
checktheroads-hyperglass/hyperglass/api/__init__.py

272 lines
7.8 KiB
Python
Raw Normal View History

2020-01-21 17:27:57 -07:00
"""hyperglass REST API & Web UI."""
2020-02-03 02:34:50 -07:00
# Standard Library
2020-07-23 09:10:26 -07:00
import sys
2020-02-03 02:34:50 -07:00
from typing import List
2020-01-21 17:27:57 -07:00
from pathlib import Path
2020-02-03 02:34:50 -07:00
# Third Party
2020-01-21 17:27:57 -07:00
from fastapi import FastAPI
2020-04-18 23:19:12 -07:00
from fastapi.exceptions import ValidationError, RequestValidationError
from starlette.responses import JSONResponse
2020-02-03 02:34:50 -07:00
from starlette.exceptions import HTTPException as StarletteHTTPException
from fastapi.openapi.utils import get_openapi
2020-01-21 17:27:57 -07:00
from starlette.staticfiles import StaticFiles
2020-02-03 02:34:50 -07:00
from starlette.middleware.cors import CORSMiddleware
2020-01-21 17:27:57 -07:00
2020-02-03 02:34:50 -07:00
# Project
2020-04-14 10:24:20 -07:00
from hyperglass.log import log
from hyperglass.util import cpu_count
from hyperglass.constants import TRANSPORT_REST, __version__
2020-02-03 02:34:50 -07:00
from hyperglass.api.events import on_startup, on_shutdown
2020-04-18 11:34:23 -07:00
from hyperglass.api.routes import (
docs,
2020-07-17 01:06:19 -07:00
info,
2020-04-18 11:34:23 -07:00
query,
queries,
routers,
communities,
2020-04-18 23:19:12 -07:00
import_certificate,
2020-04-18 11:34:23 -07:00
)
2020-01-21 17:27:57 -07:00
from hyperglass.exceptions import HyperglassError
from hyperglass.configuration import URL_DEV, STATIC_PATH, params, devices
2020-02-03 02:34:50 -07:00
from hyperglass.api.error_handlers import (
app_handler,
http_handler,
default_handler,
validation_handler,
)
from hyperglass.models.api.response import (
2020-02-03 02:34:50 -07:00
QueryError,
2020-07-17 01:06:19 -07:00
InfoResponse,
2020-02-03 02:34:50 -07:00
QueryResponse,
RoutersResponse,
2020-04-18 11:34:23 -07:00
CommunityResponse,
2020-02-03 02:34:50 -07:00
SupportedQueryResponse,
)
2020-01-21 17:27:57 -07:00
2020-02-03 02:34:50 -07:00
WORKING_DIR = Path(__file__).parent
EXAMPLES_DIR = WORKING_DIR / "examples"
UI_DIR = STATIC_PATH / "ui"
CUSTOM_DIR = STATIC_PATH / "custom"
IMAGES_DIR = STATIC_PATH / "images"
2020-02-03 02:34:50 -07:00
EXAMPLE_DEVICES_PY = EXAMPLES_DIR / "devices.py"
EXAMPLE_QUERIES_PY = EXAMPLES_DIR / "queries.py"
EXAMPLE_QUERY_PY = EXAMPLES_DIR / "query.py"
EXAMPLE_DEVICES_CURL = EXAMPLES_DIR / "devices.sh"
EXAMPLE_QUERIES_CURL = EXAMPLES_DIR / "queries.sh"
EXAMPLE_QUERY_CURL = EXAMPLES_DIR / "query.sh"
2020-01-21 17:27:57 -07:00
ASGI_PARAMS = {
2020-01-28 08:59:27 -07:00
"host": str(params.listen_address),
"port": params.listen_port,
"debug": params.debug,
2020-04-13 01:06:03 -07:00
"workers": cpu_count(2),
2020-01-21 17:27:57 -07:00
}
2020-02-03 02:34:50 -07:00
DOCS_PARAMS = {}
if params.docs.enable:
DOCS_PARAMS.update({"openapi_url": params.docs.openapi_uri})
if params.docs.mode == "redoc":
DOCS_PARAMS.update({"docs_url": None, "redoc_url": params.docs.uri})
elif params.docs.mode == "swagger":
DOCS_PARAMS.update({"docs_url": params.docs.uri, "redoc_url": None})
2020-01-21 17:27:57 -07:00
for directory in (UI_DIR, IMAGES_DIR):
if not directory.exists():
log.warning("Directory '{d}' does not exist, creating...", d=str(directory))
directory.mkdir()
2020-01-21 17:27:57 -07:00
# Main App Definition
app = FastAPI(
2020-01-28 08:59:27 -07:00
debug=params.debug,
title=params.site_title,
description=params.site_description,
2020-01-21 17:27:57 -07:00
version=__version__,
default_response_class=JSONResponse,
2020-02-03 02:34:50 -07:00
**DOCS_PARAMS,
2020-01-21 17:27:57 -07:00
)
2020-01-21 20:03:47 -07:00
# Add Event Handlers
for startup in on_startup:
app.add_event_handler("startup", startup)
for shutdown in on_shutdown:
app.add_event_handler("shutdown", shutdown)
2020-01-21 17:27:57 -07:00
# HTTP Error Handler
app.add_exception_handler(StarletteHTTPException, http_handler)
# Backend Application Error Handler
app.add_exception_handler(HyperglassError, app_handler)
2020-04-18 23:19:12 -07:00
# Request Validation Error Handler
2020-01-26 02:14:42 -07:00
app.add_exception_handler(RequestValidationError, validation_handler)
2020-04-18 23:19:12 -07:00
# App Validation Error Handler
app.add_exception_handler(ValidationError, validation_handler)
2020-01-26 02:14:42 -07:00
# Uncaught Error Handler
app.add_exception_handler(Exception, default_handler)
2020-01-21 17:27:57 -07:00
def _custom_openapi():
"""Generate custom OpenAPI config."""
openapi_schema = get_openapi(
2020-02-03 02:34:50 -07:00
title=params.docs.title.format(site_title=params.site_title),
2020-01-21 17:27:57 -07:00
version=__version__,
2020-02-03 02:34:50 -07:00
description=params.docs.description,
2020-01-21 17:27:57 -07:00
routes=app.routes,
)
2020-07-17 01:06:19 -07:00
openapi_schema["info"]["x-logo"] = {
"url": "/images/light" + params.web.logo.light.suffix
}
2020-02-03 02:34:50 -07:00
query_samples = []
queries_samples = []
devices_samples = []
with EXAMPLE_QUERY_CURL.open("r") as e:
example = e.read()
query_samples.append(
{"lang": "cURL", "source": example % str(params.docs.base_url)}
)
with EXAMPLE_QUERY_PY.open("r") as e:
example = e.read()
query_samples.append(
{"lang": "Python", "source": example % str(params.docs.base_url)}
)
with EXAMPLE_DEVICES_CURL.open("r") as e:
example = e.read()
queries_samples.append(
{"lang": "cURL", "source": example % str(params.docs.base_url)}
)
with EXAMPLE_DEVICES_PY.open("r") as e:
example = e.read()
queries_samples.append(
{"lang": "Python", "source": example % str(params.docs.base_url)}
)
with EXAMPLE_QUERIES_CURL.open("r") as e:
example = e.read()
devices_samples.append(
{"lang": "cURL", "source": example % str(params.docs.base_url)}
)
with EXAMPLE_QUERIES_PY.open("r") as e:
example = e.read()
devices_samples.append(
{"lang": "Python", "source": example % str(params.docs.base_url)}
)
openapi_schema["paths"]["/api/query/"]["post"]["x-code-samples"] = query_samples
openapi_schema["paths"]["/api/devices"]["get"]["x-code-samples"] = devices_samples
openapi_schema["paths"]["/api/queries"]["get"]["x-code-samples"] = queries_samples
2020-01-21 17:27:57 -07:00
app.openapi_schema = openapi_schema
return app.openapi_schema
2020-01-28 08:59:27 -07:00
CORS_ORIGINS = params.cors_origins.copy()
if params.developer_mode:
2020-01-21 17:27:57 -07:00
CORS_ORIGINS.append(URL_DEV)
# CORS Configuration
app.add_middleware(
CORSMiddleware,
allow_origins=CORS_ORIGINS,
allow_methods=["GET", "POST", "OPTIONS"],
allow_headers=["*"],
)
2020-07-17 01:06:19 -07:00
app.add_api_route(
path="/api/info",
endpoint=info,
methods=["GET"],
response_model=InfoResponse,
response_class=JSONResponse,
summary=params.docs.info.summary,
description=params.docs.info.description,
tags=[params.docs.info.title],
)
2020-02-01 02:24:52 -10:00
app.add_api_route(
2020-02-01 12:50:12 -10:00
path="/api/devices",
endpoint=routers,
methods=["GET"],
2020-02-03 02:34:50 -07:00
response_model=List[RoutersResponse],
response_class=JSONResponse,
2020-02-01 12:50:12 -10:00
summary=params.docs.devices.summary,
description=params.docs.devices.description,
tags=[params.docs.devices.title],
2020-02-01 02:24:52 -10:00
)
2020-04-18 11:34:23 -07:00
app.add_api_route(
path="/api/communities",
endpoint=communities,
methods=["GET"],
response_model=List[CommunityResponse],
summary=params.docs.communities.summary,
tags=[params.docs.communities.title],
)
2020-02-01 02:24:52 -10:00
app.add_api_route(
2020-02-01 12:50:12 -10:00
path="/api/queries",
endpoint=queries,
methods=["GET"],
response_class=JSONResponse,
2020-02-03 02:34:50 -07:00
response_model=List[SupportedQueryResponse],
2020-02-01 12:50:12 -10:00
summary=params.docs.queries.summary,
description=params.docs.queries.description,
tags=[params.docs.queries.title],
2020-02-01 02:24:52 -10:00
)
2020-04-18 11:34:23 -07:00
2020-01-21 17:27:57 -07:00
app.add_api_route(
path="/api/query/",
endpoint=query,
methods=["POST"],
2020-02-01 12:50:12 -10:00
summary=params.docs.query.summary,
description=params.docs.query.description,
2020-02-03 02:34:50 -07:00
responses={
400: {"model": QueryError, "description": "Request Content Error"},
422: {"model": QueryError, "description": "Request Format Error"},
500: {"model": QueryError, "description": "Server Error"},
},
2020-01-21 17:27:57 -07:00
response_model=QueryResponse,
2020-02-01 12:50:12 -10:00
tags=[params.docs.query.title],
response_class=JSONResponse,
2020-01-21 17:27:57 -07:00
)
2020-02-01 16:11:01 -10:00
# Enable certificate import route only if a device using
# hyperglass-agent is defined.
2020-07-30 01:30:01 -07:00
if [n for n in devices.all_nos if n in TRANSPORT_REST]:
app.add_api_route(
path="/api/import-agent-certificate/",
endpoint=import_certificate,
methods=["POST"],
include_in_schema=False,
)
2020-02-01 16:11:01 -10:00
if params.docs.enable:
app.add_api_route(path=params.docs.uri, endpoint=docs, include_in_schema=False)
2020-02-03 02:34:50 -07:00
app.openapi = _custom_openapi
2020-09-28 12:37:44 -07:00
log.debug("API Docs config: {}", app.openapi())
2020-02-01 16:11:01 -10:00
2020-01-21 17:27:57 -07:00
app.mount("/images", StaticFiles(directory=IMAGES_DIR), name="images")
app.mount("/custom", StaticFiles(directory=CUSTOM_DIR), name="custom")
2020-01-21 17:27:57 -07:00
app.mount("/", StaticFiles(directory=UI_DIR, html=True), name="ui")
2020-04-13 01:06:03 -07:00
def start(**kwargs):
2020-01-21 17:27:57 -07:00
"""Start the web server with Uvicorn ASGI."""
# Third Party
2020-01-21 17:27:57 -07:00
import uvicorn
2020-07-23 09:10:26 -07:00
try:
uvicorn.run("hyperglass.api:app", **ASGI_PARAMS, **kwargs)
except KeyboardInterrupt:
sys.exit(1)