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

208 lines
6.3 KiB
Python
Raw Normal View History

2020-01-21 17:27:57 -07:00
"""API Routes."""
2020-02-03 02:34:50 -07:00
# Standard Library
2020-05-29 17:47:53 -07:00
import json
import time
2021-09-15 00:57:45 -07:00
import typing as t
2020-06-26 12:23:11 -07:00
from datetime import datetime
2020-02-03 02:34:50 -07:00
# Third Party
from fastapi import HTTPException, BackgroundTasks
2020-01-21 17:27:57 -07:00
from starlette.requests import Request
2020-02-03 02:34:50 -07:00
from fastapi.openapi.docs import get_redoc_html, get_swagger_ui_html
2020-02-03 02:34:50 -07:00
# Project
2020-04-18 23:18:50 -07:00
from hyperglass.log import log
2021-09-15 00:57:45 -07:00
from hyperglass.state import use_state
from hyperglass.external import Webhook, bgptools
2021-09-11 13:56:20 -07:00
from hyperglass.api.tasks import process_headers
2020-07-17 01:06:19 -07:00
from hyperglass.constants import __version__
from hyperglass.exceptions import HyperglassError
from hyperglass.execution.main import execute
# Local
from .fake_output import fake_output
2021-09-15 00:57:45 -07:00
if t.TYPE_CHECKING:
# Project
from hyperglass.models.api import Query
2021-09-15 00:57:45 -07:00
STATE = use_state()
async def send_webhook(query_data: "Query", request: Request, timestamp: datetime):
2021-09-11 13:56:20 -07:00
"""If webhooks are enabled, get request info and send a webhook."""
try:
2021-09-15 00:57:45 -07:00
if STATE.params.logging.http is not None:
headers = await process_headers(headers=request.headers)
if headers.get("x-real-ip") is not None:
host = headers["x-real-ip"]
elif headers.get("x-forwarded-for") is not None:
host = headers["x-forwarded-for"]
else:
host = request.client.host
network_info = await bgptools.network_info(host)
2021-09-15 00:57:45 -07:00
async with Webhook(STATE.params.logging.http) as hook:
await hook.send(
query={
**query_data.export_dict(pretty=True),
"headers": headers,
"source": host,
2020-07-13 01:54:38 -07:00
"network": network_info.get(host, {}),
"timestamp": timestamp,
}
)
except Exception as err:
2021-09-15 00:57:45 -07:00
log.error("Error sending webhook to {}: {}", STATE.params.logging.http.provider, str(err))
2020-04-15 02:12:01 -07:00
2021-09-15 00:57:45 -07:00
async def query(query_data: "Query", request: Request, background_tasks: BackgroundTasks):
"""Ingest request data pass it to the backend application to perform the query."""
2020-06-26 12:23:11 -07:00
timestamp = datetime.utcnow()
background_tasks.add_task(send_webhook, query_data, request, timestamp)
2020-04-13 01:05:24 -07:00
# Initialize cache
2021-09-15 00:57:45 -07:00
cache = STATE.redis
2020-04-13 01:05:24 -07:00
log.debug("Initialized cache {}", repr(cache))
# Use hashed query_data string as key for for k/v cache store so
# each command output value is unique.
2021-09-15 00:57:45 -07:00
cache_key = f"hyperglass.query.{query_data.digest()}"
# Define cache entry expiry time
2021-09-15 00:57:45 -07:00
cache_timeout = STATE.params.cache.timeout
2020-09-28 12:37:44 -07:00
log.debug("Cache Timeout: {}", cache_timeout)
log.info("Starting query execution for query {}", query_data.summary)
2020-04-16 09:30:20 -07:00
2021-09-15 00:57:45 -07:00
cache_response = cache.get_dict(cache_key, "output")
2020-04-16 23:43:02 -07:00
2020-07-17 01:43:17 -07:00
json_output = False
if query_data.device.structured_output and query_data.query_type in (
"bgp_route",
"bgp_community",
"bgp_aspath",
):
json_output = True
2020-04-16 23:43:02 -07:00
cached = False
runtime = 65535
2020-04-16 23:43:02 -07:00
if cache_response:
2020-09-28 12:37:44 -07:00
log.debug("Query {} exists in cache", cache_key)
2020-04-19 09:50:52 -07:00
2020-04-16 23:43:02 -07:00
# If a cached response exists, reset the expiration time.
2021-09-15 00:57:45 -07:00
cache.expire(cache_key, seconds=cache_timeout)
2020-04-16 23:43:02 -07:00
cached = True
runtime = 0
2021-09-15 00:57:45 -07:00
timestamp = cache.get_dict(cache_key, "timestamp")
2020-04-16 23:43:02 -07:00
elif not cache_response:
2020-09-28 12:37:44 -07:00
log.debug("No existing cache entry for query {}", cache_key)
2021-09-12 15:09:24 -07:00
log.debug("Created new cache key {} entry for query {}", cache_key, query_data.summary)
2020-04-18 07:58:46 -07:00
timestamp = query_data.timestamp
starttime = time.time()
2021-09-15 00:57:45 -07:00
if STATE.params.fake_output:
# Return fake, static data for development purposes, if enabled.
cache_output = await fake_output(json_output)
else:
# Pass request to execution module
cache_output = await execute(query_data)
endtime = time.time()
elapsedtime = round(endtime - starttime, 4)
2020-09-28 12:37:44 -07:00
log.debug("Query {} took {} seconds to run.", cache_key, elapsedtime)
2020-04-18 07:58:46 -07:00
if cache_output is None:
2021-09-15 00:57:45 -07:00
raise HyperglassError(message=STATE.params.messages.general, alert="danger")
# Create a cache entry
2020-07-17 01:43:17 -07:00
if json_output:
2020-05-29 17:47:53 -07:00
raw_output = json.dumps(cache_output)
else:
raw_output = str(cache_output)
2021-09-15 00:57:45 -07:00
cache.set_dict(cache_key, "output", raw_output)
cache.set_dict(cache_key, "timestamp", timestamp)
cache.expire(cache_key, seconds=cache_timeout)
2020-09-28 12:37:44 -07:00
log.debug("Added cache entry for query: {}", cache_key)
2020-04-16 23:43:02 -07:00
runtime = int(round(elapsedtime, 0))
# If it does, return the cached entry
2021-09-15 00:57:45 -07:00
cache_response = cache.get_dict(cache_key, "output")
2020-07-13 01:54:38 -07:00
response_format = "text/plain"
2020-07-17 01:43:17 -07:00
if json_output:
2020-05-29 17:47:53 -07:00
response_format = "application/json"
2020-09-28 12:37:44 -07:00
log.debug("Cache match for {}:\n{}", cache_key, cache_response)
log.success("Completed query execution for query {}", query_data.summary)
2020-04-16 23:43:02 -07:00
return {
"output": cache_response,
"id": cache_key,
"cached": cached,
"runtime": runtime,
2020-04-18 07:58:46 -07:00
"timestamp": timestamp,
2020-05-29 17:47:53 -07:00
"format": response_format,
2020-04-18 07:58:46 -07:00
"random": query_data.random(),
2020-04-16 23:43:02 -07:00
"level": "success",
"keywords": [],
}
2020-01-21 17:27:57 -07:00
async def docs():
"""Serve custom docs."""
2021-09-15 00:57:45 -07:00
if STATE.params.docs.enable:
2020-01-21 17:27:57 -07:00
docs_func_map = {"swagger": get_swagger_ui_html, "redoc": get_redoc_html}
2021-09-15 00:57:45 -07:00
docs_func = docs_func_map[STATE.params.docs.mode]
2020-01-21 17:27:57 -07:00
return docs_func(
2021-09-15 00:57:45 -07:00
openapi_url=STATE.params.docs.openapi_url, title=STATE.params.site_title + " - API Docs"
2020-01-21 17:27:57 -07:00
)
else:
raise HTTPException(detail="Not found", status_code=404)
2021-09-11 13:56:20 -07:00
async def router(id: str):
"""Get a device's API-facing attributes."""
2021-09-15 00:57:45 -07:00
return STATE.devices[id].export_api()
2020-02-01 02:24:52 -10:00
2020-04-18 11:34:23 -07:00
2021-09-11 13:56:20 -07:00
async def routers():
"""Serve list of configured routers and attributes."""
2021-09-15 00:57:45 -07:00
return STATE.devices.export_api()
2020-04-18 11:34:23 -07:00
2020-02-01 02:24:52 -10:00
async def queries():
"""Serve list of enabled query types."""
2021-09-15 00:57:45 -07:00
return STATE.params.queries.list
2020-02-01 02:24:52 -10:00
2020-07-17 01:06:19 -07:00
async def info():
"""Serve general information about this instance of hyperglass."""
return {
2021-09-15 00:57:45 -07:00
"name": STATE.params.site_title,
"organization": STATE.params.org_name,
"primary_asn": int(STATE.params.primary_asn),
2021-09-11 13:56:20 -07:00
"version": __version__,
2020-07-17 01:06:19 -07:00
}
async def ui_props():
"""Serve UI configration."""
2021-09-15 00:57:45 -07:00
return STATE.ui_params
endpoints = [query, docs, routers, info, ui_props]