2020-06-10 14:26:24 -05:00
|
|
|
package flux
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
|
|
|
"github.com/grafana/grafana/pkg/infra/log"
|
|
|
|
"github.com/grafana/grafana/pkg/models"
|
|
|
|
"github.com/grafana/grafana/pkg/tsdb"
|
|
|
|
influxdb2 "github.com/influxdata/influxdb-client-go"
|
2020-06-22 12:19:26 -05:00
|
|
|
"github.com/influxdata/influxdb-client-go/api"
|
2020-06-10 14:26:24 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
glog log.Logger
|
|
|
|
)
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
glog = log.New("tsdb.influx_flux")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Query builds flux queries, executes them, and returns the results.
|
|
|
|
func Query(ctx context.Context, dsInfo *models.DataSource, tsdbQuery *tsdb.TsdbQuery) (*tsdb.Response, error) {
|
2020-08-13 10:50:53 -05:00
|
|
|
glog.Debug("Received a query", "query", *tsdbQuery)
|
2020-06-10 14:26:24 -05:00
|
|
|
tRes := &tsdb.Response{
|
|
|
|
Results: make(map[string]*tsdb.QueryResult),
|
|
|
|
}
|
|
|
|
runner, err := RunnerFromDataSource(dsInfo)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-07-31 16:11:51 -05:00
|
|
|
defer runner.client.Close()
|
2020-06-10 14:26:24 -05:00
|
|
|
|
|
|
|
for _, query := range tsdbQuery.Queries {
|
|
|
|
qm, err := GetQueryModelTSDB(query, tsdbQuery.TimeRange, dsInfo)
|
|
|
|
if err != nil {
|
|
|
|
tRes.Results[query.RefId] = &tsdb.QueryResult{Error: err}
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2020-08-13 10:50:53 -05:00
|
|
|
res := executeQuery(context.Background(), *qm, runner, 50)
|
2020-06-10 14:26:24 -05:00
|
|
|
|
|
|
|
tRes.Results[query.RefId] = backendDataResponseToTSDBResponse(&res, query.RefId)
|
|
|
|
}
|
|
|
|
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 {
|
2020-06-22 12:19:26 -05:00
|
|
|
runQuery(ctx context.Context, q string) (*api.QueryTableResult, error)
|
2020-06-10 14:26:24 -05:00
|
|
|
}
|
|
|
|
|
2020-08-13 10:50:53 -05:00
|
|
|
// runQuery executes fluxQuery against the Runner's organization and returns a Flux typed result.
|
2020-06-22 12:19:26 -05:00
|
|
|
func (r *Runner) runQuery(ctx context.Context, fluxQuery string) (*api.QueryTableResult, error) {
|
2020-08-14 11:40:02 -05:00
|
|
|
qa := r.client.QueryAPI(r.org)
|
2020-08-13 10:50:53 -05:00
|
|
|
return qa.Query(ctx, fluxQuery)
|
2020-06-10 14:26:24 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// RunnerFromDataSource creates a runner from the datasource model (the datasource instance's configuration).
|
|
|
|
func RunnerFromDataSource(dsInfo *models.DataSource) (*Runner, error) {
|
|
|
|
org := dsInfo.JsonData.Get("organization").MustString("")
|
|
|
|
if org == "" {
|
|
|
|
return nil, fmt.Errorf("missing organization in datasource configuration")
|
|
|
|
}
|
|
|
|
|
|
|
|
url := dsInfo.Url
|
|
|
|
if url == "" {
|
2020-08-13 10:50:53 -05:00
|
|
|
return nil, fmt.Errorf("missing URL from datasource configuration")
|
2020-06-10 14:26:24 -05:00
|
|
|
}
|
|
|
|
token, found := dsInfo.SecureJsonData.DecryptedValue("token")
|
|
|
|
if !found {
|
|
|
|
return nil, fmt.Errorf("token is missing from datasource configuration and is needed to use Flux")
|
|
|
|
}
|
|
|
|
|
2020-08-14 11:40:02 -05:00
|
|
|
opts := influxdb2.DefaultOptions()
|
|
|
|
hc, err := dsInfo.GetHttpClient()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
opts.HTTPOptions().SetHTTPClient(hc)
|
2020-06-10 14:26:24 -05:00
|
|
|
return &Runner{
|
2020-08-14 11:40:02 -05:00
|
|
|
client: influxdb2.NewClientWithOptions(url, token, opts),
|
2020-06-10 14:26:24 -05:00
|
|
|
org: org,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// backendDataResponseToTSDBResponse takes the SDK's style response and changes it into a
|
|
|
|
// tsdb.QueryResult. This is a wrapper so less of existing code needs to be changed. This should
|
|
|
|
// be able to be removed in the near future https://github.com/grafana/grafana/pull/25472.
|
|
|
|
func backendDataResponseToTSDBResponse(dr *backend.DataResponse, refID string) *tsdb.QueryResult {
|
|
|
|
qr := &tsdb.QueryResult{RefId: refID}
|
|
|
|
|
2020-06-18 06:45:55 -05:00
|
|
|
qr.Error = dr.Error
|
2020-06-10 14:26:24 -05:00
|
|
|
|
|
|
|
if dr.Frames != nil {
|
|
|
|
qr.Dataframes = tsdb.NewDecodedDataFrames(dr.Frames)
|
|
|
|
}
|
|
|
|
return qr
|
|
|
|
}
|