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>
pull/34946/head
Marcus Efraimsson 4 years ago committed by GitHub
parent 56e0efbb56
commit b3e9087557
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      pkg/expr/service_test.go
  2. 2
      pkg/plugins/app_plugin.go
  3. 20
      pkg/plugins/backendplugin/coreplugin/core_plugin.go
  4. 116
      pkg/plugins/backendplugin/coreplugin/query_endpoint_adapter.go
  5. 25
      pkg/plugins/backendplugin/grpcplugin/client.go
  6. 53
      pkg/plugins/backendplugin/grpcplugin/client_v2.go
  7. 10
      pkg/plugins/backendplugin/grpcplugin/grpc_plugin.go
  8. 6
      pkg/plugins/backendplugin/ifaces.go
  9. 62
      pkg/plugins/backendplugin/manager/manager.go
  10. 9
      pkg/plugins/backendplugin/manager/manager_test.go
  11. 34
      pkg/plugins/datasource_plugin.go
  12. 99
      pkg/plugins/datasource_plugin_wrapper_v2.go
  13. 2
      pkg/plugins/ifaces.go
  14. 20
      pkg/plugins/manager/manager.go
  15. 12
      pkg/plugins/manager/manager_test.go
  16. 8
      pkg/plugins/renderer_plugin.go
  17. 6
      pkg/plugins/tsdb.go
  18. 27
      pkg/tsdb/cloudwatch/cloudwatch.go
  19. 115
      pkg/tsdb/data_plugin_adapter.go
  20. 34
      pkg/tsdb/service.go
  21. 47
      pkg/tsdb/service_test.go

@ -113,7 +113,3 @@ func (me *mockEndpoint) DataQuery(ctx context.Context, ds *models.DataSource, qu
type fakeBackendPM struct { type fakeBackendPM struct {
backendplugin.Manager backendplugin.Manager
} }
func (pm fakeBackendPM) GetDataPlugin(string) interface{} {
return nil
}

@ -71,7 +71,7 @@ func (app *AppPlugin) Load(decoder *json.Decoder, base *PluginBase, backendPlugi
if app.Backend { if app.Backend {
cmd := ComposePluginStartCommand(app.Executable) cmd := ComposePluginStartCommand(app.Executable)
fullpath := filepath.Join(base.PluginDir, cmd) fullpath := filepath.Join(base.PluginDir, cmd)
factory := grpcplugin.NewBackendPlugin(app.Id, fullpath, grpcplugin.PluginStartFuncs{}) factory := grpcplugin.NewBackendPlugin(app.Id, fullpath)
if err := backendPluginManager.RegisterAndStart(context.Background(), app.Id, factory); err != nil { if err := backendPluginManager.RegisterAndStart(context.Background(), app.Id, factory); err != nil {
return nil, errutil.Wrapf(err, "failed to register backend plugin") return nil, errutil.Wrapf(err, "failed to register backend plugin")
} }

@ -5,10 +5,7 @@ import (
"github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/backendplugin" "github.com/grafana/grafana/pkg/plugins/backendplugin"
"github.com/grafana/grafana/pkg/plugins/backendplugin/instrumentation"
) )
// corePlugin represents a plugin that's part of Grafana core. // corePlugin represents a plugin that's part of Grafana core.
@ -43,15 +40,6 @@ func (cp *corePlugin) Logger() log.Logger {
return cp.logger return cp.logger
} }
//nolint: staticcheck // plugins.DataResponse deprecated
func (cp *corePlugin) DataQuery(ctx context.Context, dsInfo *models.DataSource,
tsdbQuery plugins.DataQuery) (plugins.DataResponse, error) {
// TODO: Inline the adapter, since it shouldn't be necessary
adapter := newQueryEndpointAdapter(cp.pluginID, cp.logger, instrumentation.InstrumentQueryDataHandler(
cp.QueryDataHandler))
return adapter.DataQuery(ctx, dsInfo, tsdbQuery)
}
func (cp *corePlugin) Start(ctx context.Context) error { func (cp *corePlugin) Start(ctx context.Context) error {
return nil return nil
} }
@ -88,6 +76,14 @@ func (cp *corePlugin) CheckHealth(ctx context.Context, req *backend.CheckHealthR
return nil, backendplugin.ErrMethodNotImplemented return nil, backendplugin.ErrMethodNotImplemented
} }
func (cp *corePlugin) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
if cp.QueryDataHandler != nil {
return cp.QueryDataHandler.QueryData(ctx, req)
}
return nil, backendplugin.ErrMethodNotImplemented
}
func (cp *corePlugin) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error { func (cp *corePlugin) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
if cp.CallResourceHandler != nil { if cp.CallResourceHandler != nil {
return cp.CallResourceHandler.CallResource(ctx, req, sender) return cp.CallResourceHandler.CallResource(ctx, req, sender)

@ -1,116 +0,0 @@
package coreplugin
import (
"context"
"time"
"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/plugins"
"github.com/grafana/grafana/pkg/plugins/adapters"
)
// nolint:staticcheck // plugins.DataPlugin deprecated
func newQueryEndpointAdapter(pluginID string, logger log.Logger, handler backend.QueryDataHandler) plugins.DataPlugin {
return &queryEndpointAdapter{
pluginID: pluginID,
logger: logger,
handler: handler,
}
}
type queryEndpointAdapter struct {
pluginID string
logger log.Logger
handler backend.QueryDataHandler
}
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,
}, nil
}
// nolint:staticcheck // plugins.DataQuery deprecated
func (a *queryEndpointAdapter) DataQuery(ctx context.Context, ds *models.DataSource, query plugins.DataQuery) (
plugins.DataResponse, error) {
instanceSettings, err := modelToInstanceSettings(ds)
if err != nil {
return plugins.DataResponse{}, err
}
req := &backend.QueryDataRequest{
PluginContext: backend.PluginContext{
OrgID: ds.OrgId,
PluginID: a.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 := a.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
}

@ -38,13 +38,8 @@ func newClientConfig(executablePath string, env []string, logger log.Logger,
} }
} }
// StartFunc callback function called when a plugin with current plugin protocol version is started. // StartRendererFunc callback function called when a renderer plugin is started.
type StartFunc func(pluginID string, client *Client, logger log.Logger) error type StartRendererFunc func(pluginID string, renderer pluginextensionv2.RendererPlugin, logger log.Logger) error
// PluginStartFuncs functions called for plugin when started.
type PluginStartFuncs struct {
OnStart StartFunc
}
// PluginDescriptor is a descriptor used for registering backend plugins. // PluginDescriptor is a descriptor used for registering backend plugins.
type PluginDescriptor struct { type PluginDescriptor struct {
@ -52,7 +47,7 @@ type PluginDescriptor struct {
executablePath string executablePath string
managed bool managed bool
versionedPlugins map[int]goplugin.PluginSet versionedPlugins map[int]goplugin.PluginSet
startFns PluginStartFuncs startRendererFn StartRendererFunc
} }
// getV2PluginSet returns list of plugins supported on v2. // getV2PluginSet returns list of plugins supported on v2.
@ -67,7 +62,7 @@ func getV2PluginSet() goplugin.PluginSet {
} }
// NewBackendPlugin creates a new backend plugin factory used for registering a backend plugin. // NewBackendPlugin creates a new backend plugin factory used for registering a backend plugin.
func NewBackendPlugin(pluginID, executablePath string, startFns PluginStartFuncs) backendplugin.PluginFactoryFunc { func NewBackendPlugin(pluginID, executablePath string) backendplugin.PluginFactoryFunc {
return newPlugin(PluginDescriptor{ return newPlugin(PluginDescriptor{
pluginID: pluginID, pluginID: pluginID,
executablePath: executablePath, executablePath: executablePath,
@ -75,12 +70,11 @@ func NewBackendPlugin(pluginID, executablePath string, startFns PluginStartFuncs
versionedPlugins: map[int]goplugin.PluginSet{ versionedPlugins: map[int]goplugin.PluginSet{
grpcplugin.ProtocolVersion: getV2PluginSet(), grpcplugin.ProtocolVersion: getV2PluginSet(),
}, },
startFns: startFns,
}) })
} }
// NewRendererPlugin creates a new renderer plugin factory used for registering a backend renderer plugin. // NewRendererPlugin creates a new renderer plugin factory used for registering a backend renderer plugin.
func NewRendererPlugin(pluginID, executablePath string, startFns PluginStartFuncs) backendplugin.PluginFactoryFunc { func NewRendererPlugin(pluginID, executablePath string, startFn StartRendererFunc) backendplugin.PluginFactoryFunc {
return newPlugin(PluginDescriptor{ return newPlugin(PluginDescriptor{
pluginID: pluginID, pluginID: pluginID,
executablePath: executablePath, executablePath: executablePath,
@ -88,13 +82,6 @@ func NewRendererPlugin(pluginID, executablePath string, startFns PluginStartFunc
versionedPlugins: map[int]goplugin.PluginSet{ versionedPlugins: map[int]goplugin.PluginSet{
grpcplugin.ProtocolVersion: getV2PluginSet(), grpcplugin.ProtocolVersion: getV2PluginSet(),
}, },
startFns: startFns, startRendererFn: startFn,
}) })
} }
// Client client for communicating with a plugin using the current (v2) plugin protocol.
type Client struct {
DataPlugin grpcplugin.DataClient
RendererPlugin pluginextensionv2.RendererPlugin
StreamClient grpcplugin.StreamClient
}

@ -11,11 +11,9 @@ import (
"github.com/grafana/grafana-plugin-sdk-go/genproto/pluginv2" "github.com/grafana/grafana-plugin-sdk-go/genproto/pluginv2"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/plugins/backendplugin" "github.com/grafana/grafana/pkg/plugins/backendplugin"
"github.com/grafana/grafana/pkg/plugins/backendplugin/instrumentation"
"github.com/grafana/grafana/pkg/plugins/backendplugin/pluginextensionv2" "github.com/grafana/grafana/pkg/plugins/backendplugin/pluginextensionv2"
"github.com/grafana/grafana/pkg/util/errutil" "github.com/grafana/grafana/pkg/util/errutil"
"github.com/hashicorp/go-plugin" "github.com/hashicorp/go-plugin"
"google.golang.org/grpc"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
) )
@ -69,7 +67,7 @@ func newClientV2(descriptor PluginDescriptor, logger log.Logger, rpcClient plugi
if rawData != nil { if rawData != nil {
if dataClient, ok := rawData.(grpcplugin.DataClient); ok { if dataClient, ok := rawData.(grpcplugin.DataClient); ok {
c.DataClient = instrumentDataClient(dataClient) c.DataClient = dataClient
} }
} }
@ -85,13 +83,8 @@ func newClientV2(descriptor PluginDescriptor, logger log.Logger, rpcClient plugi
} }
} }
if descriptor.startFns.OnStart != nil { if descriptor.startRendererFn != nil {
client := &Client{ if err := descriptor.startRendererFn(descriptor.pluginID, c.RendererPlugin, logger); err != nil {
DataPlugin: c.DataClient,
RendererPlugin: c.RendererPlugin,
StreamClient: c.StreamClient,
}
if err := descriptor.startFns.OnStart(descriptor.pluginID, client, logger); err != nil {
return nil, err return nil, err
} }
} }
@ -137,6 +130,25 @@ func (c *clientV2) CheckHealth(ctx context.Context, req *backend.CheckHealthRequ
return backend.FromProto().CheckHealthResponse(protoResp), nil return backend.FromProto().CheckHealthResponse(protoResp), nil
} }
func (c *clientV2) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
if c.DataClient == nil {
return nil, backendplugin.ErrMethodNotImplemented
}
protoReq := backend.ToProto().QueryDataRequest(req)
protoResp, err := c.DataClient.QueryData(ctx, protoReq)
if err != nil {
if status.Code(err) == codes.Unimplemented {
return nil, backendplugin.ErrMethodNotImplemented
}
return nil, errutil.Wrap("Failed to query data", err)
}
return backend.FromProto().QueryDataResponse(protoResp)
}
func (c *clientV2) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error { func (c *clientV2) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
if c.ResourceClient == nil { if c.ResourceClient == nil {
return backendplugin.ErrMethodNotImplemented return backendplugin.ErrMethodNotImplemented
@ -226,24 +238,3 @@ func (c *clientV2) RunStream(ctx context.Context, req *backend.RunStreamRequest,
} }
} }
} }
type dataClientQueryDataFunc func(ctx context.Context, req *pluginv2.QueryDataRequest, opts ...grpc.CallOption) (*pluginv2.QueryDataResponse, error)
func (fn dataClientQueryDataFunc) QueryData(ctx context.Context, req *pluginv2.QueryDataRequest, opts ...grpc.CallOption) (*pluginv2.QueryDataResponse, error) {
return fn(ctx, req, opts...)
}
func instrumentDataClient(plugin grpcplugin.DataClient) grpcplugin.DataClient {
if plugin == nil {
return nil
}
return dataClientQueryDataFunc(func(ctx context.Context, req *pluginv2.QueryDataRequest, opts ...grpc.CallOption) (*pluginv2.QueryDataResponse, error) {
var resp *pluginv2.QueryDataResponse
err := instrumentation.InstrumentQueryDataRequest(req.PluginContext.PluginId, func() (innerErr error) {
resp, innerErr = plugin.QueryData(ctx, req)
return
})
return resp, err
})
}

@ -14,6 +14,7 @@ import (
type pluginClient interface { type pluginClient interface {
backend.CollectMetricsHandler backend.CollectMetricsHandler
backend.CheckHealthHandler backend.CheckHealthHandler
backend.QueryDataHandler
backend.CallResourceHandler backend.CallResourceHandler
backend.StreamHandler backend.StreamHandler
} }
@ -137,6 +138,15 @@ func (p *grpcPlugin) CheckHealth(ctx context.Context, req *backend.CheckHealthRe
return pluginClient.CheckHealth(ctx, req) return pluginClient.CheckHealth(ctx, req)
} }
func (p *grpcPlugin) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
pluginClient, ok := p.getPluginClient()
if !ok {
return nil, backendplugin.ErrPluginUnavailable
}
return pluginClient.QueryData(ctx, req)
}
func (p *grpcPlugin) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error { func (p *grpcPlugin) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
pluginClient, ok := p.getPluginClient() pluginClient, ok := p.getPluginClient()
if !ok { if !ok {

@ -24,13 +24,12 @@ type Manager interface {
CollectMetrics(ctx context.Context, pluginID string) (*backend.CollectMetricsResult, error) CollectMetrics(ctx context.Context, pluginID string) (*backend.CollectMetricsResult, error)
// CheckHealth checks the health of a registered backend plugin. // CheckHealth checks the health of a registered backend plugin.
CheckHealth(ctx context.Context, pCtx backend.PluginContext) (*backend.CheckHealthResult, error) CheckHealth(ctx context.Context, pCtx backend.PluginContext) (*backend.CheckHealthResult, error)
// QueryData query data from a registered backend plugin.
QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error)
// CallResource calls a plugin resource. // CallResource calls a plugin resource.
CallResource(pluginConfig backend.PluginContext, ctx *models.ReqContext, path string) CallResource(pluginConfig backend.PluginContext, ctx *models.ReqContext, path string)
// Get plugin by its ID. // Get plugin by its ID.
Get(pluginID string) (Plugin, bool) Get(pluginID string) (Plugin, bool)
// GetDataPlugin gets a DataPlugin with a certain ID or nil if it doesn't exist.
// TODO: interface{} is the return type in order to break a dependency cycle. Should be plugins.DataPlugin.
GetDataPlugin(pluginID string) interface{}
} }
// Plugin is the backend plugin interface. // Plugin is the backend plugin interface.
@ -45,6 +44,7 @@ type Plugin interface {
IsDecommissioned() bool IsDecommissioned() bool
backend.CollectMetricsHandler backend.CollectMetricsHandler
backend.CheckHealthHandler backend.CheckHealthHandler
backend.QueryDataHandler
backend.CallResourceHandler backend.CallResourceHandler
backend.StreamHandler backend.StreamHandler
} }

@ -17,7 +17,6 @@ import (
"github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/backendplugin" "github.com/grafana/grafana/pkg/plugins/backendplugin"
"github.com/grafana/grafana/pkg/plugins/backendplugin/instrumentation" "github.com/grafana/grafana/pkg/plugins/backendplugin/instrumentation"
"github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/registry"
@ -151,6 +150,10 @@ func (m *manager) Get(pluginID string) (backendplugin.Plugin, bool) {
p, ok := m.plugins[pluginID] p, ok := m.plugins[pluginID]
m.pluginsMu.RUnlock() m.pluginsMu.RUnlock()
if ok && p.IsDecommissioned() {
return nil, false
}
return p, ok return p, ok
} }
@ -181,21 +184,6 @@ func (m *manager) getAzureEnvironmentVariables() []string {
return variables return variables
} }
//nolint: staticcheck // plugins.DataPlugin deprecated
func (m *manager) GetDataPlugin(pluginID string) interface{} {
p, _ := m.Get(pluginID)
if p == nil {
return nil
}
if dataPlugin, ok := p.(plugins.DataPlugin); ok {
return dataPlugin
}
return nil
}
// start starts a managed backend plugin // start starts a managed backend plugin
func (m *manager) start(ctx context.Context, p backendplugin.Plugin) { func (m *manager) start(ctx context.Context, p backendplugin.Plugin) {
if !p.IsManaged() { if !p.IsManaged() {
@ -244,10 +232,7 @@ func (m *manager) stop(ctx context.Context) {
// CollectMetrics collects metrics from a registered backend plugin. // CollectMetrics collects metrics from a registered backend plugin.
func (m *manager) CollectMetrics(ctx context.Context, pluginID string) (*backend.CollectMetricsResult, error) { func (m *manager) CollectMetrics(ctx context.Context, pluginID string) (*backend.CollectMetricsResult, error) {
m.pluginsMu.RLock() p, registered := m.Get(pluginID)
p, registered := m.plugins[pluginID]
m.pluginsMu.RUnlock()
if !registered { if !registered {
return nil, backendplugin.ErrPluginNotRegistered return nil, backendplugin.ErrPluginNotRegistered
} }
@ -279,10 +264,7 @@ func (m *manager) CheckHealth(ctx context.Context, pluginContext backend.PluginC
}, nil }, nil
} }
m.pluginsMu.RLock() p, registered := m.Get(pluginContext.PluginID)
p, registered := m.plugins[pluginContext.PluginID]
m.pluginsMu.RUnlock()
if !registered { if !registered {
return nil, backendplugin.ErrPluginNotRegistered return nil, backendplugin.ErrPluginNotRegistered
} }
@ -308,15 +290,39 @@ func (m *manager) CheckHealth(ctx context.Context, pluginContext backend.PluginC
return resp, nil return resp, nil
} }
func (m *manager) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
p, registered := m.Get(req.PluginContext.PluginID)
if !registered {
return nil, backendplugin.ErrPluginNotRegistered
}
var resp *backend.QueryDataResponse
err := instrumentation.InstrumentQueryDataRequest(p.PluginID(), func() (innerErr error) {
resp, innerErr = p.QueryData(ctx, req)
return
})
if err != nil {
if errors.Is(err, backendplugin.ErrMethodNotImplemented) {
return nil, err
}
if errors.Is(err, backendplugin.ErrPluginUnavailable) {
return nil, err
}
return nil, errutil.Wrap("failed to query data", err)
}
return resp, nil
}
type keepCookiesJSONModel struct { type keepCookiesJSONModel struct {
KeepCookies []string `json:"keepCookies"` KeepCookies []string `json:"keepCookies"`
} }
func (m *manager) callResourceInternal(w http.ResponseWriter, req *http.Request, pCtx backend.PluginContext) error { func (m *manager) callResourceInternal(w http.ResponseWriter, req *http.Request, pCtx backend.PluginContext) error {
m.pluginsMu.RLock() p, registered := m.Get(pCtx.PluginID)
p, registered := m.plugins[pCtx.PluginID]
m.pluginsMu.RUnlock()
if !registered { if !registered {
return backendplugin.ErrPluginNotRegistered return backendplugin.ErrPluginNotRegistered
} }

@ -367,6 +367,7 @@ type testPlugin struct {
decommissioned bool decommissioned bool
backend.CollectMetricsHandlerFunc backend.CollectMetricsHandlerFunc
backend.CheckHealthHandlerFunc backend.CheckHealthHandlerFunc
backend.QueryDataHandlerFunc
backend.CallResourceHandlerFunc backend.CallResourceHandlerFunc
mutex sync.RWMutex mutex sync.RWMutex
} }
@ -441,6 +442,14 @@ func (tp *testPlugin) CheckHealth(ctx context.Context, req *backend.CheckHealthR
return nil, backendplugin.ErrMethodNotImplemented return nil, backendplugin.ErrMethodNotImplemented
} }
func (tp *testPlugin) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
if tp.QueryDataHandlerFunc != nil {
return tp.QueryDataHandlerFunc(ctx, req)
}
return nil, backendplugin.ErrMethodNotImplemented
}
func (tp *testPlugin) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error { func (tp *testPlugin) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
if tp.CallResourceHandlerFunc != nil { if tp.CallResourceHandlerFunc != nil {
return tp.CallResourceHandlerFunc(ctx, req, sender) return tp.CallResourceHandlerFunc(ctx, req, sender)

@ -3,11 +3,8 @@ package plugins
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"fmt"
"path/filepath" "path/filepath"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins/backendplugin" "github.com/grafana/grafana/pkg/plugins/backendplugin"
"github.com/grafana/grafana/pkg/plugins/backendplugin/grpcplugin" "github.com/grafana/grafana/pkg/plugins/backendplugin/grpcplugin"
"github.com/grafana/grafana/pkg/util/errutil" "github.com/grafana/grafana/pkg/util/errutil"
@ -32,9 +29,6 @@ type DataSourcePlugin struct {
Backend bool `json:"backend,omitempty"` Backend bool `json:"backend,omitempty"`
Executable string `json:"executable,omitempty"` Executable string `json:"executable,omitempty"`
SDK bool `json:"sdk,omitempty"` SDK bool `json:"sdk,omitempty"`
client *grpcplugin.Client
logger log.Logger
} }
func (p *DataSourcePlugin) Load(decoder *json.Decoder, base *PluginBase, backendPluginManager backendplugin.Manager) ( func (p *DataSourcePlugin) Load(decoder *json.Decoder, base *PluginBase, backendPluginManager backendplugin.Manager) (
@ -46,9 +40,7 @@ func (p *DataSourcePlugin) Load(decoder *json.Decoder, base *PluginBase, backend
if p.Backend { if p.Backend {
cmd := ComposePluginStartCommand(p.Executable) cmd := ComposePluginStartCommand(p.Executable)
fullpath := filepath.Join(base.PluginDir, cmd) fullpath := filepath.Join(base.PluginDir, cmd)
factory := grpcplugin.NewBackendPlugin(p.Id, fullpath, grpcplugin.PluginStartFuncs{ factory := grpcplugin.NewBackendPlugin(p.Id, fullpath)
OnStart: p.onPluginStart,
})
if err := backendPluginManager.RegisterAndStart(context.Background(), p.Id, factory); err != nil { if err := backendPluginManager.RegisterAndStart(context.Background(), p.Id, factory); err != nil {
return nil, errutil.Wrapf(err, "failed to register backend plugin") return nil, errutil.Wrapf(err, "failed to register backend plugin")
} }
@ -56,27 +48,3 @@ func (p *DataSourcePlugin) Load(decoder *json.Decoder, base *PluginBase, backend
return p, nil return p, nil
} }
func (p *DataSourcePlugin) DataQuery(ctx context.Context, dsInfo *models.DataSource, query DataQuery) (DataResponse, error) {
if !p.CanHandleDataQueries() {
return DataResponse{}, fmt.Errorf("plugin %q can't handle data queries", p.Id)
}
endpoint := newDataSourcePluginWrapperV2(p.logger, p.Id, p.Type, p.client.DataPlugin)
return endpoint.Query(ctx, dsInfo, query)
}
func (p *DataSourcePlugin) CanHandleDataQueries() bool {
return p.client != nil
}
func (p *DataSourcePlugin) onPluginStart(pluginID string, client *grpcplugin.Client, logger log.Logger) error {
if client.DataPlugin == nil {
return nil
}
p.client = client
p.logger = logger
return nil
}

@ -1,99 +0,0 @@
package plugins
import (
"context"
"fmt"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/backend/grpcplugin"
"github.com/grafana/grafana-plugin-sdk-go/genproto/pluginv2"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins/adapters"
"github.com/grafana/grafana/pkg/services/oauthtoken"
)
func newDataSourcePluginWrapperV2(log log.Logger, pluginId, pluginType string, dataClient grpcplugin.DataClient) *DatasourcePluginWrapperV2 {
return &DatasourcePluginWrapperV2{DataClient: dataClient, logger: log, pluginId: pluginId, pluginType: pluginType}
}
type DatasourcePluginWrapperV2 struct {
grpcplugin.DataClient
logger log.Logger
pluginId string
pluginType string
}
func (tw *DatasourcePluginWrapperV2) Query(ctx context.Context, ds *models.DataSource, query DataQuery) (DataResponse, error) {
instanceSettings, err := adapters.ModelToInstanceSettings(ds)
if err != nil {
return 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)
}
}
pbQuery := &pluginv2.QueryDataRequest{
PluginContext: &pluginv2.PluginContext{
OrgId: ds.OrgId,
PluginId: tw.pluginId,
User: backend.ToProto().User(adapters.BackendUserFromSignedInUser(query.User)),
DataSourceInstanceSettings: backend.ToProto().DataSourceInstanceSettings(instanceSettings),
},
Queries: []*pluginv2.DataQuery{},
Headers: query.Headers,
}
for _, q := range query.Queries {
modelJSON, err := q.Model.MarshalJSON()
if err != nil {
return DataResponse{}, err
}
pbQuery.Queries = append(pbQuery.Queries, &pluginv2.DataQuery{
Json: modelJSON,
IntervalMS: q.IntervalMS,
RefId: q.RefID,
MaxDataPoints: q.MaxDataPoints,
TimeRange: &pluginv2.TimeRange{
ToEpochMS: query.TimeRange.GetToAsMsEpoch(),
FromEpochMS: query.TimeRange.GetFromAsMsEpoch(),
},
QueryType: q.QueryType,
})
}
pbRes, err := tw.DataClient.QueryData(ctx, pbQuery)
if err != nil {
return DataResponse{}, err
}
tR := DataResponse{
Results: make(map[string]DataQueryResult, len(pbRes.Responses)),
}
for refID, pRes := range pbRes.Responses {
qr := DataQueryResult{
RefID: refID,
Dataframes: NewEncodedDataFrames(pRes.Frames),
}
if len(pRes.JsonMeta) != 0 {
qr.Meta = simplejson.NewFromAny(pRes.JsonMeta)
}
if pRes.Error != "" {
qr.Error = fmt.Errorf(pRes.Error)
qr.ErrorString = pRes.Error
}
tR.Results[refID] = qr
}
return tR, nil
}

@ -13,8 +13,6 @@ type Manager interface {
Renderer() *RendererPlugin Renderer() *RendererPlugin
// GetDataSource gets a data source plugin with a certain ID. // GetDataSource gets a data source plugin with a certain ID.
GetDataSource(id string) *DataSourcePlugin GetDataSource(id string) *DataSourcePlugin
// GetDataPlugin gets a data plugin with a certain ID.
GetDataPlugin(id string) DataPlugin
// GetPlugin gets a plugin with a certain ID. // GetPlugin gets a plugin with a certain ID.
GetPlugin(id string) *PluginBase GetPlugin(id string) *PluginBase
// GetApp gets an app plugin with a certain ID. // GetApp gets an app plugin with a certain ID.

@ -745,26 +745,6 @@ func collectPluginFilesWithin(rootDir string) ([]string, error) {
return files, err return files, err
} }
// GetDataPlugin gets a DataPlugin with a certain name. If none is found, nil is returned.
//nolint: staticcheck // plugins.DataPlugin deprecated
func (pm *PluginManager) GetDataPlugin(id string) plugins.DataPlugin {
pm.pluginsMu.RLock()
defer pm.pluginsMu.RUnlock()
if p := pm.GetDataSource(id); p != nil && p.CanHandleDataQueries() {
return p
}
// XXX: Might other plugins implement DataPlugin?
p := pm.BackendPluginManager.GetDataPlugin(id)
if p != nil {
return p.(plugins.DataPlugin)
}
return nil
}
func (pm *PluginManager) StaticRoutes() []*plugins.PluginStaticRoute { func (pm *PluginManager) StaticRoutes() []*plugins.PluginStaticRoute {
return pm.staticRoutes return pm.staticRoutes
} }

@ -551,8 +551,6 @@ func verifyBundledPluginCatalogue(t *testing.T, pm *PluginManager) {
} }
type fakeBackendPluginManager struct { type fakeBackendPluginManager struct {
backendplugin.Manager
registeredPlugins []string registeredPlugins []string
} }
@ -566,6 +564,10 @@ func (f *fakeBackendPluginManager) RegisterAndStart(ctx context.Context, pluginI
return nil return nil
} }
func (f *fakeBackendPluginManager) Get(pluginID string) (backendplugin.Plugin, bool) {
return nil, false
}
func (f *fakeBackendPluginManager) UnregisterAndStop(ctx context.Context, pluginID string) error { func (f *fakeBackendPluginManager) UnregisterAndStop(ctx context.Context, pluginID string) error {
var result []string var result []string
@ -600,9 +602,15 @@ func (f *fakeBackendPluginManager) CheckHealth(ctx context.Context, pCtx backend
return nil, nil return nil, nil
} }
func (f *fakeBackendPluginManager) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
return nil, nil
}
func (f *fakeBackendPluginManager) CallResource(pluginConfig backend.PluginContext, ctx *models.ReqContext, path string) { func (f *fakeBackendPluginManager) CallResource(pluginConfig backend.PluginContext, ctx *models.ReqContext, path string) {
} }
var _ backendplugin.Manager = &fakeBackendPluginManager{}
type fakePluginInstaller struct { type fakePluginInstaller struct {
installCount int installCount int
uninstallCount int uninstallCount int

@ -30,9 +30,7 @@ func (r *RendererPlugin) Load(decoder *json.Decoder, base *PluginBase,
cmd := ComposePluginStartCommand("plugin_start") cmd := ComposePluginStartCommand("plugin_start")
fullpath := filepath.Join(base.PluginDir, cmd) fullpath := filepath.Join(base.PluginDir, cmd)
factory := grpcplugin.NewRendererPlugin(r.Id, fullpath, grpcplugin.PluginStartFuncs{ factory := grpcplugin.NewRendererPlugin(r.Id, fullpath, r.onPluginStart)
OnStart: r.onPluginStart,
})
if err := backendPluginManager.Register(r.Id, factory); err != nil { if err := backendPluginManager.Register(r.Id, factory); err != nil {
return nil, errutil.Wrapf(err, "failed to register backend plugin") return nil, errutil.Wrapf(err, "failed to register backend plugin")
} }
@ -48,7 +46,7 @@ func (r *RendererPlugin) Start(ctx context.Context) error {
return nil return nil
} }
func (r *RendererPlugin) onPluginStart(pluginID string, client *grpcplugin.Client, logger log.Logger) error { func (r *RendererPlugin) onPluginStart(pluginID string, renderer pluginextensionv2.RendererPlugin, logger log.Logger) error {
r.GrpcPluginV2 = client.RendererPlugin r.GrpcPluginV2 = renderer
return nil return nil
} }

@ -236,6 +236,12 @@ type DataPlugin interface {
DataQuery(ctx context.Context, ds *models.DataSource, query DataQuery) (DataResponse, error) DataQuery(ctx context.Context, ds *models.DataSource, query DataQuery) (DataResponse, error)
} }
type DataPluginFunc func(ctx context.Context, ds *models.DataSource, query DataQuery) (DataResponse, error)
func (f DataPluginFunc) DataQuery(ctx context.Context, ds *models.DataSource, query DataQuery) (DataResponse, error) {
return f(ctx, ds, query)
}
func NewDataTimeRange(from, to string) DataTimeRange { func NewDataTimeRange(from, to string) DataTimeRange {
return DataTimeRange{ return DataTimeRange{
From: from, From: from,

@ -100,7 +100,15 @@ func newExecutor(logsService *LogsService, im instancemgmt.InstanceManager, cfg
func NewInstanceSettings() datasource.InstanceFactoryFunc { func NewInstanceSettings() datasource.InstanceFactoryFunc {
return func(settings backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) { 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) err := json.Unmarshal(settings.JSONData, &jsonData)
if err != nil { if err != nil {
@ -108,18 +116,17 @@ func NewInstanceSettings() datasource.InstanceFactoryFunc {
} }
model := datasourceInfo{ model := datasourceInfo{
profile: jsonData["profile"], profile: jsonData.Profile,
region: jsonData["defaultRegion"], region: jsonData.Region,
assumeRoleARN: jsonData["assumeRoleArn"], assumeRoleARN: jsonData.AssumeRoleARN,
externalID: jsonData["externalId"], externalID: jsonData.ExternalID,
endpoint: jsonData["endpoint"], endpoint: jsonData.Endpoint,
namespace: jsonData["customMetricsNamespaces"], namespace: jsonData.Namespace,
datasourceID: settings.ID, datasourceID: settings.ID,
} }
atStr := jsonData["authType"]
at := awsds.AuthTypeDefault at := awsds.AuthTypeDefault
switch atStr { switch jsonData.AuthType {
case "credentials": case "credentials":
at = awsds.AuthTypeSharedCreds at = awsds.AuthTypeSharedCreds
case "keys": case "keys":
@ -132,7 +139,7 @@ func NewInstanceSettings() datasource.InstanceFactoryFunc {
at = awsds.AuthTypeDefault at = awsds.AuthTypeDefault
plog.Warn("Authentication type \"arn\" is deprecated, falling back to default") plog.Warn("Authentication type \"arn\" is deprecated, falling back to default")
default: default:
plog.Warn("Unrecognized AWS authentication type", "type", atStr) plog.Warn("Unrecognized AWS authentication type", "type", jsonData.AuthType)
} }
model.authType = at model.authType = at

@ -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
}

@ -7,11 +7,11 @@ import (
"github.com/grafana/grafana/pkg/infra/httpclient" "github.com/grafana/grafana/pkg/infra/httpclient"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/backendplugin"
"github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/registry"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tsdb/azuremonitor" "github.com/grafana/grafana/pkg/tsdb/azuremonitor"
"github.com/grafana/grafana/pkg/tsdb/cloudmonitoring" "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/elasticsearch"
"github.com/grafana/grafana/pkg/tsdb/graphite" "github.com/grafana/grafana/pkg/tsdb/graphite"
"github.com/grafana/grafana/pkg/tsdb/influxdb" "github.com/grafana/grafana/pkg/tsdb/influxdb"
@ -42,13 +42,13 @@ func init() {
// Service handles data requests to data sources. // Service handles data requests to data sources.
type Service struct { type Service struct {
Cfg *setting.Cfg `inject:""` Cfg *setting.Cfg `inject:""`
CloudWatchService *cloudwatch.CloudWatchService `inject:""` PostgresService *postgres.PostgresService `inject:""`
PostgresService *postgres.PostgresService `inject:""` CloudMonitoringService *cloudmonitoring.Service `inject:""`
CloudMonitoringService *cloudmonitoring.Service `inject:""` AzureMonitorService *azuremonitor.Service `inject:""`
AzureMonitorService *azuremonitor.Service `inject:""` PluginManager plugins.Manager `inject:""`
PluginManager plugins.Manager `inject:""` BackendPluginManager backendplugin.Manager `inject:""`
HTTPClientProvider httpclient.Provider `inject:""` HTTPClientProvider httpclient.Provider `inject:""`
//nolint: staticcheck // plugins.DataPlugin deprecated //nolint: staticcheck // plugins.DataPlugin deprecated
registry map[string]func(*models.DataSource) (plugins.DataPlugin, error) registry map[string]func(*models.DataSource) (plugins.DataPlugin, error)
@ -72,25 +72,19 @@ func (s *Service) Init() error {
} }
//nolint: staticcheck // plugins.DataPlugin deprecated //nolint: staticcheck // plugins.DataPlugin deprecated
func (s *Service) HandleRequest(ctx context.Context, ds *models.DataSource, query plugins.DataQuery) ( func (s *Service) HandleRequest(ctx context.Context, ds *models.DataSource, query plugins.DataQuery) (plugins.DataResponse, error) {
plugins.DataResponse, error) { if factory, exists := s.registry[ds.Type]; exists {
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)
}
var err error var err error
plugin, err = factory(ds) plugin, err := factory(ds)
if err != nil { if err != nil {
return plugins.DataResponse{}, fmt.Errorf("could not instantiate endpoint for data plugin %q: %w", return plugins.DataResponse{}, fmt.Errorf("could not instantiate endpoint for data plugin %q: %w",
ds.Type, err) 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. // RegisterQueryHandler registers a query handler factory.

@ -4,6 +4,8 @@ import (
"context" "context"
"testing" "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/models"
"github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/backendplugin" "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"}}) exe.Return("A", plugins.DataTimeSeriesSlice{plugins.DataTimeSeries{Name: "argh"}})
res, err := svc.HandleRequest(context.TODO(), &models.DataSource{Id: 1, Type: "test"}, req) 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("A", plugins.DataTimeSeriesSlice{plugins.DataTimeSeries{Name: "argh"}})
exe.Return("B", plugins.DataTimeSeriesSlice{plugins.DataTimeSeries{Name: "barg"}}) 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) 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) { t.Run("Should fallback to backend plugin manager when handling request for query with unregistered type", func(t *testing.T) {
svc, _ := createService() 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{ req := plugins.DataQuery{
TimeRange: &plugins.DataTimeRange{},
Queries: []plugins.DataSubQuery{ 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) _, err := svc.HandleRequest(context.Background(), ds, req)
require.Error(t, err) require.NoError(t, err)
require.True(t, backendPluginManagerCalled)
}) })
} }
@ -99,17 +113,22 @@ func (e *fakeExecutor) HandleQuery(refId string, fn resultsFn) {
type fakeBackendPM struct { type fakeBackendPM struct {
backendplugin.Manager backendplugin.Manager
backend.QueryDataHandlerFunc
} }
func (pm fakeBackendPM) GetDataPlugin(string) interface{} { func (m *fakeBackendPM) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
return nil if m.QueryDataHandlerFunc != nil {
return m.QueryDataHandlerFunc.QueryData(ctx, req)
}
return nil, nil
} }
func createService() (Service, *fakeExecutor) { func createService() (Service, *fakeExecutor, *fakeBackendPM) {
s := NewService() s := NewService()
s.PluginManager = &manager.PluginManager{ fakeBackendPluginManager := &fakeBackendPM{}
BackendPluginManager: fakeBackendPM{}, s.PluginManager = &manager.PluginManager{}
} s.BackendPluginManager = fakeBackendPluginManager
e := &fakeExecutor{ e := &fakeExecutor{
//nolint: staticcheck // plugins.DataPlugin deprecated //nolint: staticcheck // plugins.DataPlugin deprecated
results: make(map[string]plugins.DataQueryResult), results: make(map[string]plugins.DataQueryResult),
@ -120,5 +139,5 @@ func createService() (Service, *fakeExecutor) {
return e, nil return e, nil
} }
return s, e return s, e, fakeBackendPluginManager
} }
Loading…
Cancel
Save