feat: add query api

Add calling prometheus interface

Change-Id: I2aeac35d91c9b94dcd0549f1a0ac7ca25f6d268f
This commit is contained in:
王晨 2021-11-23 16:53:13 +08:00 committed by wang.chen
parent 872f2f4847
commit 01582540ca
8 changed files with 593 additions and 1 deletions

View File

@ -1230,6 +1230,186 @@
}
}
},
"/api/v1/query": {
"get": {
"tags": [
"Prometheus"
],
"summary": "Prometheus Query",
"description": "Prometheus query API.",
"operationId": "prometheus_query_api_v1_query_get",
"parameters": [
{
"required": false,
"schema": {
"title": "Query",
"type": "string"
},
"name": "query",
"in": "query"
},
{
"required": false,
"schema": {
"title": "Time",
"type": "string"
},
"name": "time",
"in": "query"
},
{
"required": false,
"schema": {
"title": "Timeout",
"type": "string"
},
"name": "timeout",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrometheusQueryResponse"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UnauthorizedMessage"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/InternalServerErrorMessage"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/api/v1/query_range": {
"get": {
"tags": [
"Prometheus"
],
"summary": "Prometheus Query Range",
"description": "Prometheus query_range API.",
"operationId": "prometheus_query_range_api_v1_query_range_get",
"parameters": [
{
"required": false,
"schema": {
"title": "Query",
"type": "string"
},
"name": "query",
"in": "query"
},
{
"required": false,
"schema": {
"title": "Start",
"type": "string"
},
"name": "start",
"in": "query"
},
{
"required": false,
"schema": {
"title": "End",
"type": "string"
},
"name": "end",
"in": "query"
},
{
"required": false,
"schema": {
"title": "Step",
"type": "string"
},
"name": "step",
"in": "query"
},
{
"required": false,
"schema": {
"title": "Timeout",
"type": "string"
},
"name": "timeout",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrometheusQueryRangeResponse"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UnauthorizedMessage"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/InternalServerErrorMessage"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/api/v1/contrib/keystone_endpoints": {
"get": {
"tags": [
@ -2912,6 +3092,148 @@
}
}
},
"PrometheusQueryData": {
"title": "PrometheusQueryData",
"required": [
"result",
"resultType"
],
"type": "object",
"properties": {
"result": {
"title": "Result",
"type": "array",
"items": {
"$ref": "#/components/schemas/PrometheusQueryResult"
}
},
"resultType": {
"title": "Resulttype",
"type": "string"
}
}
},
"PrometheusQueryRangeData": {
"title": "PrometheusQueryRangeData",
"required": [
"result",
"resultType"
],
"type": "object",
"properties": {
"result": {
"title": "Result",
"type": "array",
"items": {
"$ref": "#/components/schemas/PrometheusQueryRangeResult"
}
},
"resultType": {
"title": "Resulttype",
"type": "string"
}
}
},
"PrometheusQueryRangeResponse": {
"title": "PrometheusQueryRangeResponse",
"required": [
"status"
],
"type": "object",
"properties": {
"status": {
"title": "Status",
"type": "string"
},
"data": {
"$ref": "#/components/schemas/PrometheusQueryRangeData"
},
"errorType": {
"title": "Errortype",
"type": "string"
},
"error": {
"title": "Error",
"type": "string"
},
"warnings": {
"title": "Warnings",
"type": "string"
}
}
},
"PrometheusQueryRangeResult": {
"title": "PrometheusQueryRangeResult",
"required": [
"metric",
"values"
],
"type": "object",
"properties": {
"metric": {
"title": "Metric",
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"values": {
"title": "Values",
"type": "array",
"items": {}
}
}
},
"PrometheusQueryResponse": {
"title": "PrometheusQueryResponse",
"required": [
"status"
],
"type": "object",
"properties": {
"status": {
"title": "Status",
"type": "string"
},
"data": {
"$ref": "#/components/schemas/PrometheusQueryData"
},
"errorType": {
"title": "Errortype",
"type": "string"
},
"error": {
"title": "Error",
"type": "string"
},
"warnings": {
"title": "Warnings",
"type": "string"
}
}
},
"PrometheusQueryResult": {
"title": "PrometheusQueryResult",
"required": [
"metric",
"value"
],
"type": "object",
"properties": {
"metric": {
"title": "Metric",
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"value": {
"title": "Value",
"type": "array",
"items": {}
}
}
},
"Role": {
"title": "Role",
"required": [

View File

@ -5,6 +5,10 @@ default:
database_url: mysql://root:root@localhost:3306/skyline
debug: false
log_dir: ./log
prometheus_basic_auth_password: ''
prometheus_basic_auth_user: ''
prometheus_enable_basic_auth: false
prometheus_endpoint: http://localhost:9091
secret_key: aCtmgbcUqYUy_HNVg5BDXCaeJgJQzHJXwqbXr0Nmb2o
session_name: session
developer:

View File

@ -13,11 +13,12 @@
# limitations under the License.
from fastapi import APIRouter
from skyline_apiserver.api.v1 import contrib, extension, login, policy, setting
from skyline_apiserver.api.v1 import contrib, extension, login, policy, prometheus, setting
api_router = APIRouter()
api_router.include_router(login.router, tags=["Login"])
api_router.include_router(extension.router, tags=["Extension"])
api_router.include_router(prometheus.router, tags=["Prometheus"])
api_router.include_router(contrib.router, tags=["Contrib"])
api_router.include_router(policy.router, tags=["Policy"])
api_router.include_router(setting.router, tags=["Setting"])

View File

@ -0,0 +1,174 @@
from __future__ import annotations
from fastapi import APIRouter, Depends, HTTPException, status
from httpx import codes
from skyline_apiserver import schemas
from skyline_apiserver.api import deps
from skyline_apiserver.config import CONF
from skyline_apiserver.types import constants
from skyline_apiserver.utils.httpclient import _http_request
from skyline_apiserver.utils.roles import is_system_admin_or_reader
router = APIRouter()
def get_prometheus_query_response(
resp: dict,
profile: schemas.Profile,
) -> schemas.PrometheusQueryResponse:
ret = schemas.PrometheusQueryResponse(status=resp["status"])
if "warnings" in resp:
ret.warnings = resp["warnings"]
if "errorType" in resp:
ret.errorType = resp["errorType"]
if "error" in resp:
ret.error = resp["error"]
if "data" in resp:
result = [
schemas.PrometheusQueryResult(metric=i["metric"], value=i["value"])
for i in resp["data"]["result"]
]
if not is_system_admin_or_reader(profile):
result = [
i
for i in result
if "project_id" in i.metric and i.metric["project_id"] == profile.project.id
]
data = schemas.PrometheusQueryData(
resultType=resp["data"]["resultType"],
result=result,
)
ret.data = data
return ret
def get_prometheus_query_range_response(
resp: dict,
profile: schemas.Profile,
) -> schemas.PrometheusQueryRangeResponse:
ret = schemas.PrometheusQueryRangeResponse(status=resp["status"])
if "warnings" in resp:
ret.warnings = resp["warnings"]
if "errorType" in resp:
ret.errorType = resp["errorType"]
if "error" in resp:
ret.error = resp["error"]
if "data" in resp:
result = [
schemas.PrometheusQueryRangeResult(metric=i["metric"], values=i["values"])
for i in resp["data"]["result"]
]
if not is_system_admin_or_reader(profile):
result = [
i
for i in result
if "project_id" in i.metric and i.metric["project_id"] == profile.project.id
]
data = schemas.PrometheusQueryRangeData(
resultType=resp["data"]["resultType"],
result=result,
)
ret.data = data
return ret
@router.get(
"/query",
description="Prometheus query API.",
responses={
200: {"model": schemas.PrometheusQueryResponse},
401: {"model": schemas.common.UnauthorizedMessage},
500: {"model": schemas.common.InternalServerErrorMessage},
},
response_model=schemas.PrometheusQueryResponse,
status_code=status.HTTP_200_OK,
response_description="OK",
response_model_exclude_none=True,
)
async def prometheus_query(
query: str = None,
time: str = None,
timeout: str = None,
profile: schemas.Profile = Depends(deps.get_profile_update_jwt),
) -> schemas.PrometheusQueryResponse:
kwargs = {}
if query is not None:
kwargs["query"] = query
if time is not None:
kwargs["time"] = time
if timeout is not None:
kwargs["timeout"] = timeout
auth = None
if CONF.default.prometheus_enable_basic_auth:
auth = (
CONF.default.prometheus_basic_auth_user,
CONF.default.prometheus_basic_auth_password,
)
resp = await _http_request(
url=CONF.default.prometheus_endpoint + constants.PROMETHEUS_QUERY_API,
params=kwargs,
auth=auth,
)
if resp.status_code != codes.OK:
raise HTTPException(status_code=resp.status_code, detail=resp.text)
return get_prometheus_query_response(resp.json(), profile)
@router.get(
"/query_range",
description="Prometheus query_range API.",
responses={
200: {"model": schemas.PrometheusQueryRangeResponse},
401: {"model": schemas.common.UnauthorizedMessage},
500: {"model": schemas.common.InternalServerErrorMessage},
},
response_model=schemas.PrometheusQueryRangeResponse,
status_code=status.HTTP_200_OK,
response_description="OK",
response_model_exclude_none=True,
)
async def prometheus_query_range(
query: str = None,
start: str = None,
end: str = None,
step: str = None,
timeout: str = None,
profile: schemas.Profile = Depends(deps.get_profile_update_jwt),
) -> schemas.PrometheusQueryRangeResponse:
kwargs = {}
if query is not None:
kwargs["query"] = query
if start is not None:
kwargs["start"] = start
if end is not None:
kwargs["end"] = end
if step is not None:
kwargs["step"] = step
if timeout is not None:
kwargs["timeout"] = timeout
auth = None
if CONF.default.prometheus_enable_basic_auth:
auth = (
CONF.default.prometheus_basic_auth_user,
CONF.default.prometheus_basic_auth_password,
)
resp = await _http_request(
url=CONF.default.prometheus_endpoint + constants.PROMETHEUS_QUERY_RANGE_API,
params=kwargs,
auth=auth,
)
if resp.status_code != codes.OK:
raise HTTPException(status_code=resp.status_code, detail=resp.text)
return get_prometheus_query_range_response(resp.json(), profile)

View File

@ -75,6 +75,34 @@ database_url = Opt(
default="mysql://root:root@localhost:3306/skyline",
)
prometheus_endpoint = Opt(
name="prometheus_endpoint",
description="Prometheus Endpoint",
schema=StrictStr,
default="http://localhost:9091",
)
prometheus_enable_basic_auth = Opt(
name="prometheus_enable_basic_auth",
description="Start Prometheus Basic Auth",
schema=StrictBool,
default=False,
)
prometheus_basic_auth_user = Opt(
name="prometheus_basic_auth_user",
description="Prometheus Basic Auth username",
schema=StrictStr,
default="",
)
prometheus_basic_auth_password = Opt(
name="prometheus_basic_auth_password",
description="Prometheus Basic Auth password",
schema=StrictStr,
default="",
)
GROUP_NAME = __name__.split(".")[-1]
ALL_OPTS = (
debug,
@ -85,6 +113,10 @@ ALL_OPTS = (
cors_allow_origins,
session_name,
database_url,
prometheus_endpoint,
prometheus_enable_basic_auth,
prometheus_basic_auth_user,
prometheus_basic_auth_password,
)
__all__ = ("GROUP_NAME", "ALL_OPTS")

View File

@ -35,6 +35,14 @@ from .extension import (
)
from .login import Credential, Domain, License, Payload, Profile, Project, Region, Role
from .policy import Policies, PoliciesRules
from .prometheus import (
PrometheusQueryData,
PrometheusQueryRangeData,
PrometheusQueryRangeResponse,
PrometheusQueryRangeResult,
PrometheusQueryResponse,
PrometheusQueryResult,
)
from .setting import Setting, Settings, UpdateSetting
__all__ = (
@ -70,4 +78,10 @@ __all__ = (
"Setting",
"Settings",
"UpdateSetting",
"PrometheusQueryResponse",
"PrometheusQueryData",
"PrometheusQueryResult",
"PrometheusQueryRangeResponse",
"PrometheusQueryRangeData",
"PrometheusQueryRangeResult",
)

View File

@ -0,0 +1,41 @@
from __future__ import annotations
from typing import Any, Dict, List, Optional
from pydantic import BaseModel
class PrometheusQueryResult(BaseModel):
metric: Dict[str, str]
value: List[Any]
class PrometheusQueryData(BaseModel):
result: List[PrometheusQueryResult]
resultType: str
class PrometheusQueryResponse(BaseModel):
status: str
data: Optional[PrometheusQueryData]
errorType: Optional[str]
error: Optional[str]
warnings: Optional[str]
class PrometheusQueryRangeResult(BaseModel):
metric: Dict[str, str]
values: List[Any]
class PrometheusQueryRangeData(BaseModel):
result: List[PrometheusQueryRangeResult]
resultType: str
class PrometheusQueryRangeResponse(BaseModel):
status: str
data: Optional[PrometheusQueryRangeData]
errorType: Optional[str]
error: Optional[str]
warnings: Optional[str]

View File

@ -29,6 +29,10 @@ ERR_MSG_TOKEN_REVOKED = "The token has revoked."
ERR_MSG_TOKEN_EXPIRED = "The token has expired."
ERR_MSG_TOKEN_NOTFOUND = "Token not found."
# prometheus
PROMETHEUS_QUERY_API = "/api/v1/query"
PROMETHEUS_QUERY_RANGE_API = "/api/v1/query_range"
# RESTful API
# neutron
NEUTRON_PORTS_API = "/v2.0/ports"