Chore: Refactor backend plugin manager/tsdb query data (#34944)

Move QueryData method into backend plugin manager which HandleRequest uses to 
query data from plugin SDK supported data sources. This allowed us to remove a lot 
of code no longer needed.

Ref #21510

Co-authored-by: Will Browne <wbrowne@users.noreply.github.com>
This commit is contained in:
Marcus Efraimsson
2021-06-03 14:16:58 +02:00
committed by GitHub
co-authored by Will Browne
parent 56e0efbb56
commit b3e9087557
21 changed files with 294 additions and 421 deletions
+17 -10
View File
@@ -100,7 +100,15 @@ func newExecutor(logsService *LogsService, im instancemgmt.InstanceManager, cfg
func NewInstanceSettings() datasource.InstanceFactoryFunc {
return func(settings backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) {
var jsonData map[string]string
jsonData := struct {
Profile string `json:"profile"`
Region string `json:"defaulRegion"`
AssumeRoleARN string `json:"assumeRoleArn"`
ExternalID string `json:"externalId"`
Endpoint string `json:"endpoint"`
Namespace string `json:"customMetricsNamespaces"`
AuthType string `json:"authType"`
}{}
err := json.Unmarshal(settings.JSONData, &jsonData)
if err != nil {
@@ -108,18 +116,17 @@ func NewInstanceSettings() datasource.InstanceFactoryFunc {
}
model := datasourceInfo{
profile: jsonData["profile"],
region: jsonData["defaultRegion"],
assumeRoleARN: jsonData["assumeRoleArn"],
externalID: jsonData["externalId"],
endpoint: jsonData["endpoint"],
namespace: jsonData["customMetricsNamespaces"],
profile: jsonData.Profile,
region: jsonData.Region,
assumeRoleARN: jsonData.AssumeRoleARN,
externalID: jsonData.ExternalID,
endpoint: jsonData.Endpoint,
namespace: jsonData.Namespace,
datasourceID: settings.ID,
}
atStr := jsonData["authType"]
at := awsds.AuthTypeDefault
switch atStr {
switch jsonData.AuthType {
case "credentials":
at = awsds.AuthTypeSharedCreds
case "keys":
@@ -132,7 +139,7 @@ func NewInstanceSettings() datasource.InstanceFactoryFunc {
at = awsds.AuthTypeDefault
plog.Warn("Authentication type \"arn\" is deprecated, falling back to default")
default:
plog.Warn("Unrecognized AWS authentication type", "type", atStr)
plog.Warn("Unrecognized AWS authentication type", "type", jsonData.AuthType)
}
model.authType = at
+115
View File
@@ -0,0 +1,115 @@
package tsdb
import (
"context"
"fmt"
"time"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/adapters"
"github.com/grafana/grafana/pkg/services/oauthtoken"
)
// nolint:staticcheck // plugins.DataQuery deprecated
func dataPluginQueryAdapter(pluginID string, handler backend.QueryDataHandler) plugins.DataPluginFunc {
return plugins.DataPluginFunc(func(ctx context.Context, ds *models.DataSource, query plugins.DataQuery) (plugins.DataResponse, error) {
instanceSettings, err := modelToInstanceSettings(ds)
if err != nil {
return plugins.DataResponse{}, err
}
if query.Headers == nil {
query.Headers = make(map[string]string)
}
if oauthtoken.IsOAuthPassThruEnabled(ds) {
if token := oauthtoken.GetCurrentOAuthToken(ctx, query.User); token != nil {
delete(query.Headers, "Authorization")
query.Headers["Authorization"] = fmt.Sprintf("%s %s", token.Type(), token.AccessToken)
}
}
req := &backend.QueryDataRequest{
PluginContext: backend.PluginContext{
OrgID: ds.OrgId,
PluginID: pluginID,
User: adapters.BackendUserFromSignedInUser(query.User),
DataSourceInstanceSettings: instanceSettings,
},
Queries: []backend.DataQuery{},
Headers: query.Headers,
}
for _, q := range query.Queries {
modelJSON, err := q.Model.MarshalJSON()
if err != nil {
return plugins.DataResponse{}, err
}
req.Queries = append(req.Queries, backend.DataQuery{
RefID: q.RefID,
Interval: time.Duration(q.IntervalMS) * time.Millisecond,
MaxDataPoints: q.MaxDataPoints,
TimeRange: backend.TimeRange{
From: query.TimeRange.GetFromAsTimeUTC(),
To: query.TimeRange.GetToAsTimeUTC(),
},
QueryType: q.QueryType,
JSON: modelJSON,
})
}
resp, err := handler.QueryData(ctx, req)
if err != nil {
return plugins.DataResponse{}, err
}
tR := plugins.DataResponse{
Results: make(map[string]plugins.DataQueryResult, len(resp.Responses)),
}
for refID, r := range resp.Responses {
qr := plugins.DataQueryResult{
RefID: refID,
}
for _, f := range r.Frames {
if f.RefID == "" {
f.RefID = refID
}
}
qr.Dataframes = plugins.NewDecodedDataFrames(r.Frames)
if r.Error != nil {
qr.Error = r.Error
}
tR.Results[refID] = qr
}
return tR, nil
})
}
func modelToInstanceSettings(ds *models.DataSource) (*backend.DataSourceInstanceSettings, error) {
jsonDataBytes, err := ds.JsonData.MarshalJSON()
if err != nil {
return nil, err
}
return &backend.DataSourceInstanceSettings{
ID: ds.Id,
Name: ds.Name,
URL: ds.Url,
Database: ds.Database,
User: ds.User,
BasicAuthEnabled: ds.BasicAuth,
BasicAuthUser: ds.BasicAuthUser,
JSONData: jsonDataBytes,
DecryptedSecureJSONData: ds.DecryptedValues(),
Updated: ds.Updated,
UID: ds.Uid,
}, nil
}
+14 -20
View File
@@ -7,11 +7,11 @@ import (
"github.com/grafana/grafana/pkg/infra/httpclient"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/backendplugin"
"github.com/grafana/grafana/pkg/registry"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tsdb/azuremonitor"
"github.com/grafana/grafana/pkg/tsdb/cloudmonitoring"
"github.com/grafana/grafana/pkg/tsdb/cloudwatch"
"github.com/grafana/grafana/pkg/tsdb/elasticsearch"
"github.com/grafana/grafana/pkg/tsdb/graphite"
"github.com/grafana/grafana/pkg/tsdb/influxdb"
@@ -42,13 +42,13 @@ func init() {
// Service handles data requests to data sources.
type Service struct {
Cfg *setting.Cfg `inject:""`
CloudWatchService *cloudwatch.CloudWatchService `inject:""`
PostgresService *postgres.PostgresService `inject:""`
CloudMonitoringService *cloudmonitoring.Service `inject:""`
AzureMonitorService *azuremonitor.Service `inject:""`
PluginManager plugins.Manager `inject:""`
HTTPClientProvider httpclient.Provider `inject:""`
Cfg *setting.Cfg `inject:""`
PostgresService *postgres.PostgresService `inject:""`
CloudMonitoringService *cloudmonitoring.Service `inject:""`
AzureMonitorService *azuremonitor.Service `inject:""`
PluginManager plugins.Manager `inject:""`
BackendPluginManager backendplugin.Manager `inject:""`
HTTPClientProvider httpclient.Provider `inject:""`
//nolint: staticcheck // plugins.DataPlugin deprecated
registry map[string]func(*models.DataSource) (plugins.DataPlugin, error)
@@ -72,25 +72,19 @@ func (s *Service) Init() error {
}
//nolint: staticcheck // plugins.DataPlugin deprecated
func (s *Service) HandleRequest(ctx context.Context, ds *models.DataSource, query plugins.DataQuery) (
plugins.DataResponse, error) {
plugin := s.PluginManager.GetDataPlugin(ds.Type)
if plugin == nil {
factory, exists := s.registry[ds.Type]
if !exists {
return plugins.DataResponse{}, fmt.Errorf(
"could not find plugin corresponding to data source type: %q", ds.Type)
}
func (s *Service) HandleRequest(ctx context.Context, ds *models.DataSource, query plugins.DataQuery) (plugins.DataResponse, error) {
if factory, exists := s.registry[ds.Type]; exists {
var err error
plugin, err = factory(ds)
plugin, err := factory(ds)
if err != nil {
return plugins.DataResponse{}, fmt.Errorf("could not instantiate endpoint for data plugin %q: %w",
ds.Type, err)
}
return plugin.DataQuery(ctx, ds, query)
}
return plugin.DataQuery(ctx, ds, query)
return dataPluginQueryAdapter(ds.Type, s.BackendPluginManager).DataQuery(ctx, ds, query)
}
// RegisterQueryHandler registers a query handler factory.
@@ -4,6 +4,8 @@ import (
"context"
"testing"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/backendplugin"
@@ -19,7 +21,7 @@ func TestHandleRequest(t *testing.T) {
},
}
svc, exe := createService()
svc, exe, _ := createService()
exe.Return("A", plugins.DataTimeSeriesSlice{plugins.DataTimeSeries{Name: "argh"}})
res, err := svc.HandleRequest(context.TODO(), &models.DataSource{Id: 1, Type: "test"}, req)
@@ -36,7 +38,7 @@ func TestHandleRequest(t *testing.T) {
},
}
svc, exe := createService()
svc, exe, _ := createService()
exe.Return("A", plugins.DataTimeSeriesSlice{plugins.DataTimeSeries{Name: "argh"}})
exe.Return("B", plugins.DataTimeSeriesSlice{plugins.DataTimeSeries{Name: "barg"}})
@@ -48,16 +50,28 @@ func TestHandleRequest(t *testing.T) {
require.Equal(t, "barg", res.Results["B"].Series[0].Name)
})
t.Run("Should return error when handling request for query with unknown type", func(t *testing.T) {
svc, _ := createService()
t.Run("Should fallback to backend plugin manager when handling request for query with unregistered type", func(t *testing.T) {
svc, _, manager := createService()
backendPluginManagerCalled := false
manager.QueryDataHandlerFunc = backend.QueryDataHandlerFunc(func(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
backendPluginManagerCalled = true
return &backend.QueryDataResponse{}, nil
})
ds := &models.DataSource{Id: 12, Type: "unregisteredType", JsonData: simplejson.New()}
req := plugins.DataQuery{
TimeRange: &plugins.DataTimeRange{},
Queries: []plugins.DataSubQuery{
{RefID: "A", DataSource: &models.DataSource{Id: 1, Type: "asdasdas"}},
{
RefID: "A",
DataSource: ds,
Model: simplejson.New(),
},
},
}
_, err := svc.HandleRequest(context.TODO(), &models.DataSource{Id: 12, Type: "testjughjgjg"}, req)
require.Error(t, err)
_, err := svc.HandleRequest(context.Background(), ds, req)
require.NoError(t, err)
require.True(t, backendPluginManagerCalled)
})
}
@@ -99,17 +113,22 @@ func (e *fakeExecutor) HandleQuery(refId string, fn resultsFn) {
type fakeBackendPM struct {
backendplugin.Manager
backend.QueryDataHandlerFunc
}
func (pm fakeBackendPM) GetDataPlugin(string) interface{} {
return nil
}
func createService() (Service, *fakeExecutor) {
s := NewService()
s.PluginManager = &manager.PluginManager{
BackendPluginManager: fakeBackendPM{},
func (m *fakeBackendPM) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
if m.QueryDataHandlerFunc != nil {
return m.QueryDataHandlerFunc.QueryData(ctx, req)
}
return nil, nil
}
func createService() (Service, *fakeExecutor, *fakeBackendPM) {
s := NewService()
fakeBackendPluginManager := &fakeBackendPM{}
s.PluginManager = &manager.PluginManager{}
s.BackendPluginManager = fakeBackendPluginManager
e := &fakeExecutor{
//nolint: staticcheck // plugins.DataPlugin deprecated
results: make(map[string]plugins.DataQueryResult),
@@ -120,5 +139,5 @@ func createService() (Service, *fakeExecutor) {
return e, nil
}
return s, e
return s, e, fakeBackendPluginManager
}