Graphite: Backend functions endpoint (#110771)
* Add lint rules * Backend decoupling - Add standalone files - Add graphite query type - Add logger to Service - Create logger in the ProvideService method - Use a pointer for the HTTP client provider - Update logger usage everywhere - Update tracer type - Replace simplejson with json - Add dummy CallResource and CheckHealth methods - Update tests * Update ConfigEditor imports * Update types imports * Update datasource - Switch to using semver package - Update imports * Update store imports * Update helper imports and notification creation * Update context import * Update version numbers and logic * Copy array_move from core * Test updates * Add required files and update plugin.json * Update core references and packages * Remove commented code * Update wire * Lint * Fix import * Copy null type * More lint * Update snapshot * Refactor backend - Split query logic into separate file - Move utils to separate file * Add health-check logic - Support backend healthcheck if the FF is enabled * Remove query import support as unneeded * Add test * Add util function for decoding responses * Add events types * Add resource handler * Add events handler and generic resource req handler * Tests * Update frontend - Add types - Update events function to support backend requests * Lint and typing * Lint * Add metrics find endpoint - Add types - Add generic response parser - Add endpoint - Tests * Update FE functoin to use backend endpoint * Lint * Simplify request * Update test * Metrics expand type * Extract shared logic and add metric expand endpoint * Update tests * Call metric expand from backend * Rename type for clarity * Add get resource req handler * Refactor doGraphiteRequest, parseResponse Update tests * Migrate functions endpoint to backend * Add tests * Review * Review * Fix packages * Format * Fix merge issues * Review * Fix undefined values * Extract request creation - Add method for create requests generically with tests - Replace usage in query method - Update usages in resource handlers - Update tests - Update types * Lint * Lint
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package graphite
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
@@ -16,13 +17,14 @@ import (
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
)
|
||||
|
||||
type resourceHandler[T any] func(context.Context, *datasourceInfo, T) ([]byte, int, error)
|
||||
type resourceHandler[T any] func(context.Context, *datasourceInfo, *T) ([]byte, int, error)
|
||||
|
||||
func (s *Service) newResourceMux() *http.ServeMux {
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/events", handleResourceReq[GraphiteEventsRequest](s.handleEvents, s))
|
||||
mux.HandleFunc("/metrics/find", handleResourceReq[GraphiteMetricsFindRequest](s.handleMetricsFind, s))
|
||||
mux.HandleFunc("/metrics/expand", handleResourceReq[GraphiteMetricsFindRequest](s.handleMetricsExpand, s))
|
||||
mux.HandleFunc("/events", handleResourceReq(s.handleEvents, s))
|
||||
mux.HandleFunc("/metrics/find", handleResourceReq(s.handleMetricsFind, s))
|
||||
mux.HandleFunc("/metrics/expand", handleResourceReq(s.handleMetricsExpand, s))
|
||||
mux.HandleFunc("/functions", handleResourceReq(s.handleFunctions, s))
|
||||
return mux
|
||||
}
|
||||
|
||||
@@ -39,17 +41,28 @@ func handleResourceReq[T any](handlerFn resourceHandler[T], s *Service) func(rw
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := req.Body.Close(); err != nil {
|
||||
s.logger.Warn("Failed to close response body", "err", err)
|
||||
if req.Body != nil {
|
||||
if err := req.Body.Close(); err != nil {
|
||||
s.logger.Warn("Failed to close request body", "err", err)
|
||||
writeErrorResponse(rw, http.StatusInternalServerError, fmt.Sprintf("unexpected error %v", err))
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
var parsedBody *T
|
||||
if req.Body != nil {
|
||||
body, err := io.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to read request body", "error", err)
|
||||
writeErrorResponse(rw, http.StatusInternalServerError, fmt.Sprintf("unexpected error %v", err))
|
||||
return
|
||||
}
|
||||
}()
|
||||
requestBody, err := io.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to read request body", "error", err)
|
||||
writeErrorResponse(rw, http.StatusInternalServerError, fmt.Sprintf("unexpected error %v", err))
|
||||
return
|
||||
parsedBody, err = parseRequestBody[T](body, s.logger)
|
||||
if err != nil {
|
||||
writeErrorResponse(rw, http.StatusBadRequest, fmt.Sprintf("failed to parse request body: %v", err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if handlerFn == nil {
|
||||
@@ -57,13 +70,7 @@ func handleResourceReq[T any](handlerFn resourceHandler[T], s *Service) func(rw
|
||||
return
|
||||
}
|
||||
|
||||
parsedBody, err := parseRequestBody[T](requestBody, s.logger)
|
||||
if err != nil {
|
||||
writeErrorResponse(rw, http.StatusBadRequest, fmt.Sprintf("failed to parse request body: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
response, statusCode, err := handlerFn(ctx, dsInfo, *parsedBody)
|
||||
response, statusCode, err := handlerFn(ctx, dsInfo, parsedBody)
|
||||
if err != nil {
|
||||
writeErrorResponse(rw, statusCode, fmt.Sprintf("failed to handle resource request: %v", err))
|
||||
return
|
||||
@@ -78,7 +85,7 @@ func handleResourceReq[T any](handlerFn resourceHandler[T], s *Service) func(rw
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) handleEvents(ctx context.Context, dsInfo *datasourceInfo, eventsRequestJson GraphiteEventsRequest) ([]byte, int, error) {
|
||||
func (s *Service) handleEvents(ctx context.Context, dsInfo *datasourceInfo, eventsRequestJson *GraphiteEventsRequest) ([]byte, int, error) {
|
||||
queryParams := map[string]string{
|
||||
"from": eventsRequestJson.From,
|
||||
"until": eventsRequestJson.Until,
|
||||
@@ -96,7 +103,7 @@ func (s *Service) handleEvents(ctx context.Context, dsInfo *datasourceInfo, even
|
||||
return nil, http.StatusInternalServerError, fmt.Errorf("failed to create events request %v", err)
|
||||
}
|
||||
|
||||
events, statusCode, err := doGraphiteRequest[[]GraphiteEventsResponse](ctx, dsInfo, s.logger, req)
|
||||
events, _, statusCode, err := doGraphiteRequest[[]GraphiteEventsResponse](ctx, dsInfo, s.logger, req, false)
|
||||
if err != nil {
|
||||
return nil, statusCode, fmt.Errorf("events request failed: %v", err)
|
||||
}
|
||||
@@ -112,7 +119,7 @@ func (s *Service) handleEvents(ctx context.Context, dsInfo *datasourceInfo, even
|
||||
return graphiteEventsResponse, statusCode, nil
|
||||
}
|
||||
|
||||
func (s *Service) handleMetricsFind(ctx context.Context, dsInfo *datasourceInfo, metricsFindRequestJson GraphiteMetricsFindRequest) ([]byte, int, error) {
|
||||
func (s *Service) handleMetricsFind(ctx context.Context, dsInfo *datasourceInfo, metricsFindRequestJson *GraphiteMetricsFindRequest) ([]byte, int, error) {
|
||||
if metricsFindRequestJson.Query == "" {
|
||||
return nil, http.StatusBadRequest, fmt.Errorf("query is required")
|
||||
}
|
||||
@@ -139,7 +146,7 @@ func (s *Service) handleMetricsFind(ctx context.Context, dsInfo *datasourceInfo,
|
||||
return nil, http.StatusInternalServerError, fmt.Errorf("failed to create metrics find request %v", err)
|
||||
}
|
||||
|
||||
metrics, statusCode, err := doGraphiteRequest[[]GraphiteMetricsFindResponse](ctx, dsInfo, s.logger, req)
|
||||
metrics, _, statusCode, err := doGraphiteRequest[[]GraphiteMetricsFindResponse](ctx, dsInfo, s.logger, req, false)
|
||||
if err != nil {
|
||||
return nil, statusCode, fmt.Errorf("metrics find request failed: %v", err)
|
||||
}
|
||||
@@ -152,7 +159,7 @@ func (s *Service) handleMetricsFind(ctx context.Context, dsInfo *datasourceInfo,
|
||||
return metricsFindResponse, statusCode, nil
|
||||
}
|
||||
|
||||
func (s *Service) handleMetricsExpand(ctx context.Context, dsInfo *datasourceInfo, metricsExpandRequestJson GraphiteMetricsFindRequest) ([]byte, int, error) {
|
||||
func (s *Service) handleMetricsExpand(ctx context.Context, dsInfo *datasourceInfo, metricsExpandRequestJson *GraphiteMetricsFindRequest) ([]byte, int, error) {
|
||||
if metricsExpandRequestJson.Query == "" {
|
||||
return nil, http.StatusBadRequest, fmt.Errorf("query is required")
|
||||
}
|
||||
@@ -176,7 +183,7 @@ func (s *Service) handleMetricsExpand(ctx context.Context, dsInfo *datasourceInf
|
||||
return nil, http.StatusInternalServerError, fmt.Errorf("failed to create metrics expand request %v", err)
|
||||
}
|
||||
|
||||
metrics, statusCode, err := doGraphiteRequest[GraphiteMetricsExpandResponse](ctx, dsInfo, s.logger, req)
|
||||
metrics, _, statusCode, err := doGraphiteRequest[GraphiteMetricsExpandResponse](ctx, dsInfo, s.logger, req, false)
|
||||
if err != nil {
|
||||
return nil, statusCode, fmt.Errorf("metrics expand request failed: %v", err)
|
||||
}
|
||||
@@ -196,7 +203,29 @@ func (s *Service) handleMetricsExpand(ctx context.Context, dsInfo *datasourceInf
|
||||
return metricsExpandResponse, statusCode, nil
|
||||
}
|
||||
|
||||
func doGraphiteRequest[T any](ctx context.Context, dsInfo *datasourceInfo, logger log.Logger, req *http.Request) (*T, int, error) {
|
||||
func (s *Service) handleFunctions(ctx context.Context, dsInfo *datasourceInfo, _ *any) ([]byte, int, error) {
|
||||
req, err := s.createRequest(ctx, dsInfo, URLParams{
|
||||
SubPath: "functions",
|
||||
Method: http.MethodGet,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, http.StatusInternalServerError, fmt.Errorf("failed to create functions request %v", err)
|
||||
}
|
||||
|
||||
_, rawBody, statusCode, err := doGraphiteRequest[map[string]any](ctx, dsInfo, s.logger, req, true)
|
||||
if err != nil {
|
||||
return nil, statusCode, fmt.Errorf("version request failed: %v", err)
|
||||
}
|
||||
|
||||
if rawBody == nil {
|
||||
return []byte{}, statusCode, nil
|
||||
}
|
||||
|
||||
rawBodyReplaced := bytes.ReplaceAll(*rawBody, []byte("\"default\": Infinity"), []byte("\"default\": 1e9999"))
|
||||
return rawBodyReplaced, statusCode, nil
|
||||
}
|
||||
|
||||
func doGraphiteRequest[T any](ctx context.Context, dsInfo *datasourceInfo, logger log.Logger, req *http.Request, isRaw bool) (*T, *[]byte, int, error) {
|
||||
_, span := tracing.DefaultTracer().Start(ctx, "graphite request")
|
||||
defer span.End()
|
||||
span.SetAttributes(
|
||||
@@ -209,7 +238,7 @@ func doGraphiteRequest[T any](ctx context.Context, dsInfo *datasourceInfo, logge
|
||||
if err != nil {
|
||||
span.RecordError(err)
|
||||
span.SetStatus(codes.Error, err.Error())
|
||||
return nil, http.StatusInternalServerError, fmt.Errorf("failed to complete request: %v", err)
|
||||
return nil, nil, http.StatusInternalServerError, fmt.Errorf("failed to complete request: %v", err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
@@ -218,12 +247,12 @@ func doGraphiteRequest[T any](ctx context.Context, dsInfo *datasourceInfo, logge
|
||||
}
|
||||
}()
|
||||
|
||||
parsedResponse, err := parseResponse[T](res)
|
||||
parsedResponse, rawBody, err := parseResponse[T](res, isRaw, logger)
|
||||
if err != nil {
|
||||
return nil, http.StatusInternalServerError, fmt.Errorf("failed to parse response: %v", err)
|
||||
return nil, nil, http.StatusInternalServerError, fmt.Errorf("failed to parse response: %v", err)
|
||||
}
|
||||
|
||||
return parsedResponse, res.StatusCode, nil
|
||||
return parsedResponse, rawBody, res.StatusCode, nil
|
||||
}
|
||||
|
||||
func parseRequestBody[V any](requestBody []byte, logger log.Logger) (*V, error) {
|
||||
@@ -236,19 +265,28 @@ func parseRequestBody[V any](requestBody []byte, logger log.Logger) (*V, error)
|
||||
return requestJson, nil
|
||||
}
|
||||
|
||||
func parseResponse[V any](res *http.Response) (*V, error) {
|
||||
func parseResponse[V any](res *http.Response, isRaw bool, logger log.Logger) (*V, *[]byte, error) {
|
||||
encoding := res.Header.Get("Content-Encoding")
|
||||
body, err := decode(encoding, res.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read response: %v", err)
|
||||
return nil, nil, fmt.Errorf("failed to read response: %v", err)
|
||||
}
|
||||
|
||||
if res.StatusCode/100 != 2 {
|
||||
logger.Warn("Request failed", "status", res.Status, "body", string(body))
|
||||
return nil, nil, fmt.Errorf("request failed, status: %d", res.StatusCode)
|
||||
}
|
||||
|
||||
if isRaw {
|
||||
return nil, &body, nil
|
||||
}
|
||||
|
||||
data := new(V)
|
||||
err = json.Unmarshal(body, &data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal response: %v", err)
|
||||
return nil, nil, fmt.Errorf("failed to unmarshal response: %v", err)
|
||||
}
|
||||
return data, nil
|
||||
return data, nil, nil
|
||||
}
|
||||
|
||||
func writeErrorResponse(rw http.ResponseWriter, code int, msg string) {
|
||||
|
||||
Reference in New Issue
Block a user