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": { "/api/v1/contrib/keystone_endpoints": {
"get": { "get": {
"tags": [ "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": { "Role": {
"title": "Role", "title": "Role",
"required": [ "required": [

View File

@ -5,6 +5,10 @@ default:
database_url: mysql://root:root@localhost:3306/skyline database_url: mysql://root:root@localhost:3306/skyline
debug: false debug: false
log_dir: ./log 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 secret_key: aCtmgbcUqYUy_HNVg5BDXCaeJgJQzHJXwqbXr0Nmb2o
session_name: session session_name: session
developer: developer:

View File

@ -13,11 +13,12 @@
# limitations under the License. # limitations under the License.
from fastapi import APIRouter 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 = APIRouter()
api_router.include_router(login.router, tags=["Login"]) api_router.include_router(login.router, tags=["Login"])
api_router.include_router(extension.router, tags=["Extension"]) 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(contrib.router, tags=["Contrib"])
api_router.include_router(policy.router, tags=["Policy"]) api_router.include_router(policy.router, tags=["Policy"])
api_router.include_router(setting.router, tags=["Setting"]) 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", 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] GROUP_NAME = __name__.split(".")[-1]
ALL_OPTS = ( ALL_OPTS = (
debug, debug,
@ -85,6 +113,10 @@ ALL_OPTS = (
cors_allow_origins, cors_allow_origins,
session_name, session_name,
database_url, database_url,
prometheus_endpoint,
prometheus_enable_basic_auth,
prometheus_basic_auth_user,
prometheus_basic_auth_password,
) )
__all__ = ("GROUP_NAME", "ALL_OPTS") __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 .login import Credential, Domain, License, Payload, Profile, Project, Region, Role
from .policy import Policies, PoliciesRules from .policy import Policies, PoliciesRules
from .prometheus import (
PrometheusQueryData,
PrometheusQueryRangeData,
PrometheusQueryRangeResponse,
PrometheusQueryRangeResult,
PrometheusQueryResponse,
PrometheusQueryResult,
)
from .setting import Setting, Settings, UpdateSetting from .setting import Setting, Settings, UpdateSetting
__all__ = ( __all__ = (
@ -70,4 +78,10 @@ __all__ = (
"Setting", "Setting",
"Settings", "Settings",
"UpdateSetting", "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_EXPIRED = "The token has expired."
ERR_MSG_TOKEN_NOTFOUND = "Token not found." ERR_MSG_TOKEN_NOTFOUND = "Token not found."
# prometheus
PROMETHEUS_QUERY_API = "/api/v1/query"
PROMETHEUS_QUERY_RANGE_API = "/api/v1/query_range"
# RESTful API # RESTful API
# neutron # neutron
NEUTRON_PORTS_API = "/v2.0/ports" NEUTRON_PORTS_API = "/v2.0/ports"