GCM: Improve error handling (#93474)

* Improve error handling

* More manual errorsource

* Revert some changes from original PR

* Update test
This commit is contained in:
Andreas Christou 2024-09-20 10:40:05 +01:00 committed by GitHub
parent 19c7e1f376
commit e18d029a24
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 43 additions and 26 deletions

View File

@ -23,7 +23,10 @@ type annotationEvent struct {
func (s *Service) executeAnnotationQuery(ctx context.Context, req *backend.QueryDataRequest, dsInfo datasourceInfo, queries []cloudMonitoringQueryExecutor, logger log.Logger) ( func (s *Service) executeAnnotationQuery(ctx context.Context, req *backend.QueryDataRequest, dsInfo datasourceInfo, queries []cloudMonitoringQueryExecutor, logger log.Logger) (
*backend.QueryDataResponse, error) { *backend.QueryDataResponse, error) {
resp := backend.NewQueryDataResponse() resp := backend.NewQueryDataResponse()
queryRes, dr, _, err := queries[0].run(ctx, req, s, dsInfo, logger) dr, queryRes, _, err := queries[0].run(ctx, req, s, dsInfo, logger)
if dr.Error != nil {
errorsource.AddErrorToResponse(queries[0].getRefID(), resp, dr.Error)
}
if err != nil { if err != nil {
errorsource.AddErrorToResponse(queries[0].getRefID(), resp, err) errorsource.AddErrorToResponse(queries[0].getRefID(), resp, err)
return resp, err return resp, err
@ -39,10 +42,14 @@ func (s *Service) executeAnnotationQuery(ctx context.Context, req *backend.Query
firstQuery := req.Queries[0] firstQuery := req.Queries[0]
err = json.Unmarshal(firstQuery.JSON, &tslq) err = json.Unmarshal(firstQuery.JSON, &tslq)
if err != nil { if err != nil {
logger.Error("error unmarshaling query", "error", err, "statusSource", backend.ErrorSourceDownstream)
errorsource.AddErrorToResponse(firstQuery.RefID, resp, err)
return resp, nil return resp, nil
} }
err = parseToAnnotations(req.Queries[0].RefID, queryRes, dr.(cloudMonitoringResponse), tslq.TimeSeriesList.Title, tslq.TimeSeriesList.Text)
resp.Responses[firstQuery.RefID] = *queryRes // parseToAnnotations never actually returns an error
err = parseToAnnotations(req.Queries[0].RefID, dr, queryRes.(cloudMonitoringResponse), tslq.TimeSeriesList.Title, tslq.TimeSeriesList.Text)
resp.Responses[firstQuery.RefID] = *dr
if err != nil { if err != nil {
errorsource.AddErrorToResponse(firstQuery.RefID, resp, err) errorsource.AddErrorToResponse(firstQuery.RefID, resp, err)

View File

@ -361,20 +361,21 @@ func (s *Service) executeTimeSeriesQuery(ctx context.Context, req *backend.Query
*backend.QueryDataResponse, error) { *backend.QueryDataResponse, error) {
resp := backend.NewQueryDataResponse() resp := backend.NewQueryDataResponse()
for _, queryExecutor := range queries { for _, queryExecutor := range queries {
queryRes, dr, executedQueryString, err := queryExecutor.run(ctx, req, s, dsInfo, logger) dr, queryRes, executedQueryString, err := queryExecutor.run(ctx, req, s, dsInfo, logger)
if err != nil { if err != nil {
errorsource.AddErrorToResponse(queryExecutor.getRefID(), resp, err) errorsource.AddErrorToResponse(queryExecutor.getRefID(), resp, err)
return resp, err return resp, err
} }
err = queryExecutor.parseResponse(queryRes, dr, executedQueryString, logger) err = queryExecutor.parseResponse(dr, queryRes, executedQueryString, logger)
if err != nil { if err != nil {
// Default to a plugin error if there's no source dr.Error = err
// // Default to a plugin error if there's no source
errWithSource := errorsource.SourceError(backend.ErrorSourcePlugin, err, false) errWithSource := errorsource.SourceError(backend.ErrorSourcePlugin, err, false)
queryRes.Error = errWithSource.Unwrap() dr.Error = errWithSource.Unwrap()
queryRes.ErrorSource = errWithSource.ErrorSource() dr.ErrorSource = errWithSource.ErrorSource()
} }
resp.Responses[queryExecutor.getRefID()] = *queryRes resp.Responses[queryExecutor.getRefID()] = *dr
} }
return resp, nil return resp, nil
@ -608,21 +609,21 @@ func unmarshalResponse(res *http.Response, logger log.Logger) (cloudMonitoringRe
}() }()
if res.StatusCode/100 != 2 { if res.StatusCode/100 != 2 {
logger.Error("Request failed", "status", res.Status, "body", string(body)) logger.Error("Request failed", "status", res.Status, "body", string(body), "statusSource", backend.ErrorSourceDownstream)
return cloudMonitoringResponse{}, errorsource.SourceError(backend.ErrorSourceFromHTTPStatus(res.StatusCode), fmt.Errorf("query failed: %s", string(body)), false) return cloudMonitoringResponse{}, errorsource.SourceError(backend.ErrorSourceFromHTTPStatus(res.StatusCode), fmt.Errorf("query failed: %s", string(body)), false)
} }
var data cloudMonitoringResponse var data cloudMonitoringResponse
err = json.Unmarshal(body, &data) err = json.Unmarshal(body, &data)
if err != nil { if err != nil {
logger.Error("Failed to unmarshal CloudMonitoring response", "error", err, "status", res.Status, "body", string(body)) logger.Error("Failed to unmarshal CloudMonitoring response", "error", err, "status", res.Status, "body", string(body), "statusSource", backend.ErrorSourceDownstream)
return cloudMonitoringResponse{}, fmt.Errorf("failed to unmarshal query response: %w", err) return cloudMonitoringResponse{}, fmt.Errorf("failed to unmarshal query response: %w", err)
} }
return data, nil return data, nil
} }
func addConfigData(frames data.Frames, dl string, unit string, period *string) data.Frames { func addConfigData(frames data.Frames, dl string, unit string, period *string, logger log.Logger) data.Frames {
for i := range frames { for i := range frames {
if frames[i].Fields[1].Config == nil { if frames[i].Fields[1].Config == nil {
frames[i].Fields[1].Config = &data.FieldConfig{} frames[i].Fields[1].Config = &data.FieldConfig{}
@ -646,7 +647,7 @@ func addConfigData(frames data.Frames, dl string, unit string, period *string) d
if period != nil && *period != "" { if period != nil && *period != "" {
err := addInterval(*period, frames[i].Fields[0]) err := addInterval(*period, frames[i].Fields[0])
if err != nil { if err != nil {
backend.Logger.Error("Failed to add interval", "error", err) logger.Error("Failed to add interval: %s", err, "statusSource", backend.ErrorSourceDownstream)
} }
} }
} }

View File

@ -23,7 +23,8 @@ func (promQLQ *cloudMonitoringProm) run(ctx context.Context, req *backend.QueryD
dr := &backend.DataResponse{} dr := &backend.DataResponse{}
projectName, err := s.ensureProject(ctx, dsInfo, promQLQ.parameters.ProjectName) projectName, err := s.ensureProject(ctx, dsInfo, promQLQ.parameters.ProjectName)
if err != nil { if err != nil {
return dr, backend.DataResponse{}, "", err dr.Error = err
return dr, backend.DataResponse{}, "", nil
} }
r, err := createRequest(ctx, &dsInfo, path.Join("/v1/projects", projectName, "location/global/prometheus/api/v1/query_range"), nil) r, err := createRequest(ctx, &dsInfo, path.Join("/v1/projects", projectName, "location/global/prometheus/api/v1/query_range"), nil)
if err != nil { if err != nil {
@ -43,12 +44,13 @@ func (promQLQ *cloudMonitoringProm) run(ctx context.Context, req *backend.QueryD
res, err := doRequestProm(r, dsInfo, requestBody) res, err := doRequestProm(r, dsInfo, requestBody)
if err != nil { if err != nil {
return dr, backend.DataResponse{}, "", err dr.Error = err
return dr, backend.DataResponse{}, "", nil
} }
defer func() { defer func() {
if err := res.Body.Close(); err != nil { if err := res.Body.Close(); err != nil {
s.logger.Error("Failed to close response body", "err", err) s.logger.Error("Failed to close response body", "err", err, "statusSource", backend.ErrorSourceDownstream)
} }
}() }()

View File

@ -59,8 +59,10 @@ func TestPromqlQuery(t *testing.T) {
} }
dr, parsedProm, _, err := query.run(context.Background(), &backend.QueryDataRequest{}, service, dsInfo, service.logger) dr, parsedProm, _, err := query.run(context.Background(), &backend.QueryDataRequest{}, service, dsInfo, service.logger)
require.Error(t, err) require.NoError(t, err)
require.Equal(t, "not found!", err.Error()) require.Error(t, dr.Error)
require.Equal(t, "not found!", dr.Error.Error())
require.True(t, backend.IsDownstreamError(dr.Error))
err = query.parseResponse(dr, parsedProm, "", service.logger) err = query.parseResponse(dr, parsedProm, "", service.logger)
require.NoError(t, err) require.NoError(t, err)

View File

@ -386,7 +386,7 @@ func writeResponseBytes(rw http.ResponseWriter, code int, msg []byte) {
rw.WriteHeader(code) rw.WriteHeader(code)
_, err := rw.Write(msg) _, err := rw.Write(msg)
if err != nil { if err != nil {
backend.Logger.Error("Unable to write HTTP response", "error", err) backend.Logger.Error("Unable to write HTTP response", "error", err, "statusSource", backend.ErrorSourceDownstream)
} }
} }

View File

@ -21,7 +21,7 @@ func (timeSeriesFilter *cloudMonitoringTimeSeriesList) run(ctx context.Context,
} }
func parseTimeSeriesResponse(queryRes *backend.DataResponse, func parseTimeSeriesResponse(queryRes *backend.DataResponse,
response cloudMonitoringResponse, executedQueryString string, query cloudMonitoringQueryExecutor, params url.Values, groupBys []string, _ log.Logger) error { response cloudMonitoringResponse, executedQueryString string, query cloudMonitoringQueryExecutor, params url.Values, groupBys []string, logger log.Logger) error {
frames := data.Frames{} frames := data.Frames{}
for _, series := range response.TimeSeries { for _, series := range response.TimeSeries {
@ -47,7 +47,7 @@ func parseTimeSeriesResponse(queryRes *backend.DataResponse,
if len(response.TimeSeries) > 0 { if len(response.TimeSeries) > 0 {
dl := query.buildDeepLink() dl := query.buildDeepLink()
aggregationAlignmentString := params.Get("aggregation.alignmentPeriod") aggregationAlignmentString := params.Get("aggregation.alignmentPeriod")
frames = addConfigData(frames, dl, response.Unit, &aggregationAlignmentString) frames = addConfigData(frames, dl, response.Unit, &aggregationAlignmentString, logger)
} }
queryRes.Frames = frames queryRes.Frames = frames
@ -93,6 +93,7 @@ func (timeSeriesFilter *cloudMonitoringTimeSeriesList) buildDeepLink() string {
"Failed to generate deep link: unable to parse metrics explorer URL", "Failed to generate deep link: unable to parse metrics explorer URL",
"ProjectName", timeSeriesFilter.parameters.ProjectName, "ProjectName", timeSeriesFilter.parameters.ProjectName,
"error", err, "error", err,
"statusSource", backend.ErrorSourcePlugin,
) )
} }

View File

@ -78,7 +78,7 @@ func (timeSeriesQuery *cloudMonitoringTimeSeriesQuery) parseResponse(queryRes *b
} }
if len(response.TimeSeriesData) > 0 { if len(response.TimeSeriesData) > 0 {
dl := timeSeriesQuery.buildDeepLink() dl := timeSeriesQuery.buildDeepLink()
frames = addConfigData(frames, dl, response.Unit, timeSeriesQuery.parameters.GraphPeriod) frames = addConfigData(frames, dl, response.Unit, timeSeriesQuery.parameters.GraphPeriod, logger)
} }
queryRes.Frames = frames queryRes.Frames = frames
@ -106,6 +106,7 @@ func (timeSeriesQuery *cloudMonitoringTimeSeriesQuery) buildDeepLink() string {
"Failed to generate deep link: unable to parse metrics explorer URL", "Failed to generate deep link: unable to parse metrics explorer URL",
"ProjectName", timeSeriesQuery.parameters.Query, "ProjectName", timeSeriesQuery.parameters.Query,
"error", err, "error", err,
"statusSource", backend.ErrorSourcePlugin,
) )
} }

View File

@ -61,7 +61,7 @@ func createRequest(ctx context.Context, dsInfo *datasourceInfo, proxyPass string
} }
req, err := http.NewRequestWithContext(ctx, method, dsInfo.services[cloudMonitor].url, body) req, err := http.NewRequestWithContext(ctx, method, dsInfo.services[cloudMonitor].url, body)
if err != nil { if err != nil {
backend.Logger.Error("Failed to create request", "error", err) backend.Logger.Error("Failed to create request", "error", err, "statusSource", backend.ErrorSourceDownstream)
return nil, fmt.Errorf("failed to create request: %w", err) return nil, fmt.Errorf("failed to create request: %w", err)
} }
@ -142,7 +142,8 @@ func runTimeSeriesRequest(ctx context.Context, req *backend.QueryDataRequest,
dr := &backend.DataResponse{} dr := &backend.DataResponse{}
projectName, err := s.ensureProject(ctx, dsInfo, projectName) projectName, err := s.ensureProject(ctx, dsInfo, projectName)
if err != nil { if err != nil {
return dr, cloudMonitoringResponse{}, "", err dr.Error = err
return dr, cloudMonitoringResponse{}, "", nil
} }
timeSeriesMethod := "timeSeries" timeSeriesMethod := "timeSeries"
if body != nil { if body != nil {
@ -150,7 +151,8 @@ func runTimeSeriesRequest(ctx context.Context, req *backend.QueryDataRequest,
} }
r, err := createRequest(ctx, &dsInfo, path.Join("/v3/projects", projectName, timeSeriesMethod), nil) r, err := createRequest(ctx, &dsInfo, path.Join("/v3/projects", projectName, timeSeriesMethod), nil)
if err != nil { if err != nil {
return dr, cloudMonitoringResponse{}, "", err dr.Error = err
return dr, cloudMonitoringResponse{}, "", nil
} }
span := traceReq(ctx, req, dsInfo, r, params.Encode()) span := traceReq(ctx, req, dsInfo, r, params.Encode())
@ -158,7 +160,8 @@ func runTimeSeriesRequest(ctx context.Context, req *backend.QueryDataRequest,
d, err := doRequestWithPagination(ctx, r, dsInfo, params, body, logger) d, err := doRequestWithPagination(ctx, r, dsInfo, params, body, logger)
if err != nil { if err != nil {
return dr, cloudMonitoringResponse{}, "", err dr.Error = err
return dr, cloudMonitoringResponse{}, "", nil
} }
return dr, d, r.URL.RawQuery, nil return dr, d, r.URL.RawQuery, nil