Files
beejeebus 9de769318c Add more errorsource attribution to InfluxDb datasource (#100969)
This PR adds errorsource attribution to the influxql and flux query paths
when the query model cannot be parsed, which is a user error.

It also catches cases where the datasource configuration does not
contain a scheme or host, and adds downstream attribution to those
errors.

Error handling on the influxql query path is updated to match 'all errors
are per query, and stashed on the response object' pattern.

Fixes https://github.com/grafana/oss-plugin-partnerships/issues/1250
2025-02-20 11:53:28 -05:00

87 lines
2.5 KiB
Go

package flux
import (
"context"
"fmt"
"github.com/grafana/grafana-plugin-sdk-go/backend"
influxdb2 "github.com/influxdata/influxdb-client-go/v2"
"github.com/influxdata/influxdb-client-go/v2/api"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/tsdb/influxdb/models"
)
var (
glog = log.New("tsdb.influx_flux")
)
// Query builds flux queries, executes them, and returns the results.
func Query(ctx context.Context, dsInfo *models.DatasourceInfo, tsdbQuery backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
logger := glog.FromContext(ctx)
tRes := backend.NewQueryDataResponse()
logger.Debug("Received a query", "query", tsdbQuery)
r, err := runnerFromDataSource(dsInfo)
if err != nil {
return &backend.QueryDataResponse{}, err
}
defer r.client.Close()
timeRange := tsdbQuery.Queries[0].TimeRange
for _, query := range tsdbQuery.Queries {
qm, err := getQueryModel(query, timeRange, dsInfo)
if err != nil {
tRes.Responses[query.RefID] = backend.DataResponse{
Error: err,
ErrorSource: backend.ErrorSourceDownstream,
}
continue
}
// If the default changes also update labels/placeholder in config page.
maxSeries := dsInfo.MaxSeries
res := executeQuery(ctx, logger, *qm, r, maxSeries)
tRes.Responses[query.RefID] = res
}
return tRes, nil
}
// runner is an influxdb2 Client with an attached org property and is used
// for running flux queries.
type runner struct {
client influxdb2.Client
org string
}
// This is an interface to help testing
type queryRunner interface {
runQuery(ctx context.Context, q string) (*api.QueryTableResult, error)
}
// runQuery executes fluxQuery against the Runner's organization and returns a Flux typed result.
func (r *runner) runQuery(ctx context.Context, fluxQuery string) (*api.QueryTableResult, error) {
qa := r.client.QueryAPI(r.org)
return qa.Query(ctx, fluxQuery)
}
// runnerFromDataSource creates a runner from the datasource model (the datasource instance's configuration).
func runnerFromDataSource(dsInfo *models.DatasourceInfo) (*runner, error) {
org := dsInfo.Organization
if org == "" {
return nil, fmt.Errorf("missing organization in datasource configuration")
}
url := dsInfo.URL
if url == "" {
return nil, fmt.Errorf("missing URL from datasource configuration")
}
opts := influxdb2.DefaultOptions()
opts.HTTPOptions().SetHTTPClient(dsInfo.HTTPClient)
opts.SetHTTPRequestTimeout(uint(dsInfo.Timeout.Seconds()))
return &runner{
client: influxdb2.NewClientWithOptions(url, dsInfo.Token, opts),
org: org,
}, nil
}