mirror of
https://github.com/grafana/grafana.git
synced 2025-07-31 19:22:34 +08:00
Introduce TSDB service (#31520)
* Introduce TSDB service Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> Co-authored-by: Erik Sundell <erik.sundell87@gmail.com> Co-authored-by: Will Browne <will.browne@grafana.com> Co-authored-by: Torkel Ödegaard <torkel@grafana.org> Co-authored-by: Will Browne <wbrowne@users.noreply.github.com> Co-authored-by: Zoltán Bedi <zoltan.bedi@gmail.com>
This commit is contained in:
9
pkg/plugins/backendplugin/backendplugin.go
Normal file
9
pkg/plugins/backendplugin/backendplugin.go
Normal file
@ -0,0 +1,9 @@
|
||||
// Package backendplugin contains backend plugin related logic.
|
||||
package backendplugin
|
||||
|
||||
import (
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
)
|
||||
|
||||
// PluginFactoryFunc is a function type for creating a Plugin.
|
||||
type PluginFactoryFunc func(pluginID string, logger log.Logger, env []string) (Plugin, error)
|
@ -6,14 +6,16 @@ import (
|
||||
"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/backendplugin"
|
||||
"github.com/grafana/grafana/pkg/tsdb"
|
||||
"github.com/grafana/grafana/pkg/plugins/backendplugin/instrumentation"
|
||||
)
|
||||
|
||||
// corePlugin represents a plugin that's part of Grafana core.
|
||||
type corePlugin struct {
|
||||
pluginID string
|
||||
logger log.Logger
|
||||
isDataPlugin bool
|
||||
pluginID string
|
||||
logger log.Logger
|
||||
backend.CheckHealthHandler
|
||||
backend.CallResourceHandler
|
||||
backend.QueryDataHandler
|
||||
@ -21,7 +23,7 @@ type corePlugin struct {
|
||||
|
||||
// New returns a new backendplugin.PluginFactoryFunc for creating a core (built-in) backendplugin.Plugin.
|
||||
func New(opts backend.ServeOpts) backendplugin.PluginFactoryFunc {
|
||||
return backendplugin.PluginFactoryFunc(func(pluginID string, logger log.Logger, env []string) (backendplugin.Plugin, error) {
|
||||
return func(pluginID string, logger log.Logger, env []string) (backendplugin.Plugin, error) {
|
||||
return &corePlugin{
|
||||
pluginID: pluginID,
|
||||
logger: logger,
|
||||
@ -29,7 +31,7 @@ func New(opts backend.ServeOpts) backendplugin.PluginFactoryFunc {
|
||||
CallResourceHandler: opts.CallResourceHandler,
|
||||
QueryDataHandler: opts.QueryDataHandler,
|
||||
}, nil
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (cp *corePlugin) PluginID() string {
|
||||
@ -40,11 +42,21 @@ func (cp *corePlugin) Logger() log.Logger {
|
||||
return cp.logger
|
||||
}
|
||||
|
||||
func (cp *corePlugin) CanHandleDataQueries() bool {
|
||||
return cp.isDataPlugin
|
||||
}
|
||||
|
||||
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 {
|
||||
if cp.QueryDataHandler != nil {
|
||||
tsdb.RegisterTsdbQueryEndpoint(cp.pluginID, func(dsInfo *models.DataSource) (tsdb.TsdbQueryEndpoint, error) {
|
||||
return newQueryEndpointAdapter(cp.pluginID, cp.logger, backendplugin.InstrumentQueryDataHandler(cp.QueryDataHandler)), nil
|
||||
})
|
||||
cp.isDataPlugin = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -36,11 +36,13 @@ func TestCorePlugin(t *testing.T) {
|
||||
checkHealthCalled := false
|
||||
callResourceCalled := false
|
||||
factory := coreplugin.New(backend.ServeOpts{
|
||||
CheckHealthHandler: backend.CheckHealthHandlerFunc(func(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
|
||||
CheckHealthHandler: backend.CheckHealthHandlerFunc(func(ctx context.Context,
|
||||
req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
|
||||
checkHealthCalled = true
|
||||
return nil, nil
|
||||
}),
|
||||
CallResourceHandler: backend.CallResourceHandlerFunc(func(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
|
||||
CallResourceHandler: backend.CallResourceHandlerFunc(func(ctx context.Context,
|
||||
req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
|
||||
callResourceCalled = true
|
||||
return nil
|
||||
}),
|
||||
|
@ -7,11 +7,11 @@ import (
|
||||
"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/datasource/wrapper"
|
||||
"github.com/grafana/grafana/pkg/tsdb"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/plugins/adapters"
|
||||
)
|
||||
|
||||
func newQueryEndpointAdapter(pluginID string, logger log.Logger, handler backend.QueryDataHandler) tsdb.TsdbQueryEndpoint {
|
||||
func newQueryEndpointAdapter(pluginID string, logger log.Logger, handler backend.QueryDataHandler) plugins.DataPlugin {
|
||||
return &queryEndpointAdapter{
|
||||
pluginID: pluginID,
|
||||
logger: logger,
|
||||
@ -45,17 +45,18 @@ func modelToInstanceSettings(ds *models.DataSource) (*backend.DataSourceInstance
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *queryEndpointAdapter) Query(ctx context.Context, ds *models.DataSource, query *tsdb.TsdbQuery) (*tsdb.Response, error) {
|
||||
func (a *queryEndpointAdapter) DataQuery(ctx context.Context, ds *models.DataSource, query plugins.DataQuery) (
|
||||
plugins.DataResponse, error) {
|
||||
instanceSettings, err := modelToInstanceSettings(ds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return plugins.DataResponse{}, err
|
||||
}
|
||||
|
||||
req := &backend.QueryDataRequest{
|
||||
PluginContext: backend.PluginContext{
|
||||
OrgID: ds.OrgId,
|
||||
PluginID: a.pluginID,
|
||||
User: wrapper.BackendUserFromSignedInUser(query.User),
|
||||
User: adapters.BackendUserFromSignedInUser(query.User),
|
||||
DataSourceInstanceSettings: instanceSettings,
|
||||
},
|
||||
Queries: []backend.DataQuery{},
|
||||
@ -65,11 +66,11 @@ func (a *queryEndpointAdapter) Query(ctx context.Context, ds *models.DataSource,
|
||||
for _, q := range query.Queries {
|
||||
modelJSON, err := q.Model.MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return plugins.DataResponse{}, err
|
||||
}
|
||||
req.Queries = append(req.Queries, backend.DataQuery{
|
||||
RefID: q.RefId,
|
||||
Interval: time.Duration(q.IntervalMs) * time.Millisecond,
|
||||
RefID: q.RefID,
|
||||
Interval: time.Duration(q.IntervalMS) * time.Millisecond,
|
||||
MaxDataPoints: q.MaxDataPoints,
|
||||
TimeRange: backend.TimeRange{
|
||||
From: query.TimeRange.GetFromAsTimeUTC(),
|
||||
@ -82,16 +83,16 @@ func (a *queryEndpointAdapter) Query(ctx context.Context, ds *models.DataSource,
|
||||
|
||||
resp, err := a.handler.QueryData(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return plugins.DataResponse{}, err
|
||||
}
|
||||
|
||||
tR := &tsdb.Response{
|
||||
Results: make(map[string]*tsdb.QueryResult, len(resp.Responses)),
|
||||
tR := plugins.DataResponse{
|
||||
Results: make(map[string]plugins.DataQueryResult, len(resp.Responses)),
|
||||
}
|
||||
|
||||
for refID, r := range resp.Responses {
|
||||
qr := &tsdb.QueryResult{
|
||||
RefId: refID,
|
||||
qr := plugins.DataQueryResult{
|
||||
RefID: refID,
|
||||
}
|
||||
|
||||
for _, f := range r.Frames {
|
||||
@ -100,7 +101,7 @@ func (a *queryEndpointAdapter) Query(ctx context.Context, ds *models.DataSource,
|
||||
}
|
||||
}
|
||||
|
||||
qr.Dataframes = tsdb.NewDecodedDataFrames(r.Frames)
|
||||
qr.Dataframes = plugins.NewDecodedDataFrames(r.Frames)
|
||||
|
||||
if r.Error != nil {
|
||||
qr.Error = r.Error
|
||||
|
14
pkg/plugins/backendplugin/errors.go
Normal file
14
pkg/plugins/backendplugin/errors.go
Normal file
@ -0,0 +1,14 @@
|
||||
package backendplugin
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
// ErrPluginNotRegistered error returned when plugin not registered.
|
||||
ErrPluginNotRegistered = errors.New("plugin not registered")
|
||||
// ErrHealthCheckFailed error returned when health check failed.
|
||||
ErrHealthCheckFailed = errors.New("health check failed")
|
||||
// ErrPluginUnavailable error returned when plugin is unavailable.
|
||||
ErrPluginUnavailable = errors.New("plugin unavailable")
|
||||
// ErrMethodNotImplemented error returned when plugin method not implemented.
|
||||
ErrMethodNotImplemented = errors.New("method not implemented")
|
||||
)
|
@ -8,6 +8,7 @@ import (
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/plugins/backendplugin"
|
||||
"github.com/grafana/grafana/pkg/plugins/backendplugin/instrumentation"
|
||||
"github.com/hashicorp/go-plugin"
|
||||
)
|
||||
|
||||
@ -75,7 +76,7 @@ func instrumentDatasourcePluginV1(plugin datasourceV1.DatasourcePlugin) datasour
|
||||
|
||||
return datasourceV1QueryFunc(func(ctx context.Context, req *datasourceV1.DatasourceRequest) (*datasourceV1.DatasourceResponse, error) {
|
||||
var resp *datasourceV1.DatasourceResponse
|
||||
err := backendplugin.InstrumentQueryDataRequest(req.Datasource.Type, func() (innerErr error) {
|
||||
err := instrumentation.InstrumentQueryDataRequest(req.Datasource.Type, func() (innerErr error) {
|
||||
resp, innerErr = plugin.Query(ctx, req)
|
||||
return
|
||||
})
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
"github.com/grafana/grafana-plugin-sdk-go/genproto/pluginv2"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"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/util/errutil"
|
||||
"github.com/hashicorp/go-plugin"
|
||||
@ -170,7 +171,7 @@ func instrumentDataClient(plugin grpcplugin.DataClient) grpcplugin.DataClient {
|
||||
|
||||
return dataClientQueryDataFunc(func(ctx context.Context, req *pluginv2.QueryDataRequest, opts ...grpc.CallOption) (*pluginv2.QueryDataResponse, error) {
|
||||
var resp *pluginv2.QueryDataResponse
|
||||
err := backendplugin.InstrumentQueryDataRequest(req.PluginContext.PluginId, func() (innerErr error) {
|
||||
err := instrumentation.InstrumentQueryDataRequest(req.PluginContext.PluginId, func() (innerErr error) {
|
||||
resp, innerErr = plugin.QueryData(ctx, req)
|
||||
return
|
||||
})
|
||||
|
@ -28,7 +28,7 @@ type grpcPlugin struct {
|
||||
|
||||
// newPlugin allocates and returns a new gRPC (external) backendplugin.Plugin.
|
||||
func newPlugin(descriptor PluginDescriptor) backendplugin.PluginFactoryFunc {
|
||||
return backendplugin.PluginFactoryFunc(func(pluginID string, logger log.Logger, env []string) (backendplugin.Plugin, error) {
|
||||
return func(pluginID string, logger log.Logger, env []string) (backendplugin.Plugin, error) {
|
||||
return &grpcPlugin{
|
||||
descriptor: descriptor,
|
||||
logger: logger,
|
||||
@ -36,7 +36,11 @@ func newPlugin(descriptor PluginDescriptor) backendplugin.PluginFactoryFunc {
|
||||
return plugin.NewClient(newClientConfig(descriptor.executablePath, env, logger, descriptor.versionedPlugins))
|
||||
},
|
||||
}, nil
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (p *grpcPlugin) CanHandleDataQueries() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *grpcPlugin) PluginID() string {
|
||||
|
40
pkg/plugins/backendplugin/ifaces.go
Normal file
40
pkg/plugins/backendplugin/ifaces.go
Normal file
@ -0,0 +1,40 @@
|
||||
package backendplugin
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
)
|
||||
|
||||
// Manager manages backend plugins.
|
||||
type Manager interface {
|
||||
// Register registers a backend plugin
|
||||
Register(pluginID string, factory PluginFactoryFunc) error
|
||||
// StartPlugin starts a non-managed backend plugin
|
||||
StartPlugin(ctx context.Context, pluginID string) error
|
||||
// CollectMetrics collects metrics from a registered backend plugin.
|
||||
CollectMetrics(ctx context.Context, pluginID string) (*backend.CollectMetricsResult, error)
|
||||
// CheckHealth checks the health of a registered backend plugin.
|
||||
CheckHealth(ctx context.Context, pCtx backend.PluginContext) (*backend.CheckHealthResult, error)
|
||||
// CallResource calls a plugin resource.
|
||||
CallResource(pluginConfig backend.PluginContext, ctx *models.ReqContext, path string)
|
||||
// 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.
|
||||
type Plugin interface {
|
||||
PluginID() string
|
||||
Logger() log.Logger
|
||||
Start(ctx context.Context) error
|
||||
Stop(ctx context.Context) error
|
||||
IsManaged() bool
|
||||
Exited() bool
|
||||
CanHandleDataQueries() bool
|
||||
backend.CollectMetricsHandler
|
||||
backend.CheckHealthHandler
|
||||
backend.CallResourceHandler
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
package backendplugin
|
||||
// Package instrumentation contains backend plugin instrumentation logic.
|
||||
package instrumentation
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -48,19 +49,22 @@ func instrumentPluginRequest(pluginID string, endpoint string, fn func() error)
|
||||
return err
|
||||
}
|
||||
|
||||
func instrumentCollectMetrics(pluginID string, fn func() error) error {
|
||||
// InstrumentCollectMetrics instruments collectMetrics.
|
||||
func InstrumentCollectMetrics(pluginID string, fn func() error) error {
|
||||
return instrumentPluginRequest(pluginID, "collectMetrics", fn)
|
||||
}
|
||||
|
||||
func instrumentCheckHealthRequest(pluginID string, fn func() error) error {
|
||||
// InstrumentCheckHealthRequest instruments checkHealth.
|
||||
func InstrumentCheckHealthRequest(pluginID string, fn func() error) error {
|
||||
return instrumentPluginRequest(pluginID, "checkHealth", fn)
|
||||
}
|
||||
|
||||
func instrumentCallResourceRequest(pluginID string, fn func() error) error {
|
||||
// InstrumentCallResourceRequest instruments callResource.
|
||||
func InstrumentCallResourceRequest(pluginID string, fn func() error) error {
|
||||
return instrumentPluginRequest(pluginID, "callResource", fn)
|
||||
}
|
||||
|
||||
// InstrumentQueryDataRequest instruments success rate and latency of query data request.
|
||||
// InstrumentQueryDataRequest instruments success rate and latency of query data requests.
|
||||
func InstrumentQueryDataRequest(pluginID string, fn func() error) error {
|
||||
return instrumentPluginRequest(pluginID, "queryData", fn)
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package backendplugin
|
||||
package manager
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -15,53 +15,31 @@ import (
|
||||
"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/backendplugin"
|
||||
"github.com/grafana/grafana/pkg/plugins/backendplugin/instrumentation"
|
||||
"github.com/grafana/grafana/pkg/registry"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/util/errutil"
|
||||
"github.com/grafana/grafana/pkg/util/proxyutil"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrPluginNotRegistered error returned when plugin not registered.
|
||||
ErrPluginNotRegistered = errors.New("plugin not registered")
|
||||
// ErrHealthCheckFailed error returned when health check failed.
|
||||
ErrHealthCheckFailed = errors.New("health check failed")
|
||||
// ErrPluginUnavailable error returned when plugin is unavailable.
|
||||
ErrPluginUnavailable = errors.New("plugin unavailable")
|
||||
// ErrMethodNotImplemented error returned when plugin method not implemented.
|
||||
ErrMethodNotImplemented = errors.New("method not implemented")
|
||||
)
|
||||
|
||||
func init() {
|
||||
registry.RegisterServiceWithPriority(&manager{}, registry.MediumHigh)
|
||||
}
|
||||
|
||||
// Manager manages backend plugins.
|
||||
type Manager interface {
|
||||
// Register registers a backend plugin
|
||||
Register(pluginID string, factory PluginFactoryFunc) error
|
||||
// StartPlugin starts a non-managed backend plugin
|
||||
StartPlugin(ctx context.Context, pluginID string) error
|
||||
// CollectMetrics collects metrics from a registered backend plugin.
|
||||
CollectMetrics(ctx context.Context, pluginID string) (*backend.CollectMetricsResult, error)
|
||||
// CheckHealth checks the health of a registered backend plugin.
|
||||
CheckHealth(ctx context.Context, pCtx backend.PluginContext) (*backend.CheckHealthResult, error)
|
||||
// CallResource calls a plugin resource.
|
||||
CallResource(pluginConfig backend.PluginContext, ctx *models.ReqContext, path string)
|
||||
}
|
||||
|
||||
type manager struct {
|
||||
Cfg *setting.Cfg `inject:""`
|
||||
License models.Licensing `inject:""`
|
||||
PluginRequestValidator models.PluginRequestValidator `inject:""`
|
||||
pluginsMu sync.RWMutex
|
||||
plugins map[string]Plugin
|
||||
plugins map[string]backendplugin.Plugin
|
||||
logger log.Logger
|
||||
pluginSettings map[string]pluginSettings
|
||||
}
|
||||
|
||||
func (m *manager) Init() error {
|
||||
m.plugins = make(map[string]Plugin)
|
||||
m.plugins = make(map[string]backendplugin.Plugin)
|
||||
m.logger = log.New("plugins.backend")
|
||||
m.pluginSettings = extractPluginSettings(m.Cfg)
|
||||
|
||||
@ -76,7 +54,7 @@ func (m *manager) Run(ctx context.Context) error {
|
||||
}
|
||||
|
||||
// Register registers a backend plugin
|
||||
func (m *manager) Register(pluginID string, factory PluginFactoryFunc) error {
|
||||
func (m *manager) Register(pluginID string, factory backendplugin.PluginFactoryFunc) error {
|
||||
m.logger.Debug("Registering backend plugin", "pluginId", pluginID)
|
||||
m.pluginsMu.Lock()
|
||||
defer m.pluginsMu.Unlock()
|
||||
@ -121,6 +99,19 @@ func (m *manager) Register(pluginID string, factory PluginFactoryFunc) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *manager) GetDataPlugin(pluginID string) interface{} {
|
||||
plugin := m.plugins[pluginID]
|
||||
if plugin == nil || !plugin.CanHandleDataQueries() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if dataPlugin, ok := plugin.(plugins.DataPlugin); ok {
|
||||
return dataPlugin
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// start starts all managed backend plugins
|
||||
func (m *manager) start(ctx context.Context) {
|
||||
m.pluginsMu.RLock()
|
||||
@ -143,7 +134,7 @@ func (m *manager) StartPlugin(ctx context.Context, pluginID string) error {
|
||||
p, registered := m.plugins[pluginID]
|
||||
m.pluginsMu.RUnlock()
|
||||
if !registered {
|
||||
return ErrPluginNotRegistered
|
||||
return backendplugin.ErrPluginNotRegistered
|
||||
}
|
||||
|
||||
if p.IsManaged() {
|
||||
@ -160,7 +151,7 @@ func (m *manager) stop(ctx context.Context) {
|
||||
var wg sync.WaitGroup
|
||||
for _, p := range m.plugins {
|
||||
wg.Add(1)
|
||||
go func(p Plugin, ctx context.Context) {
|
||||
go func(p backendplugin.Plugin, ctx context.Context) {
|
||||
defer wg.Done()
|
||||
p.Logger().Debug("Stopping plugin")
|
||||
if err := p.Stop(ctx); err != nil {
|
||||
@ -179,11 +170,11 @@ func (m *manager) CollectMetrics(ctx context.Context, pluginID string) (*backend
|
||||
m.pluginsMu.RUnlock()
|
||||
|
||||
if !registered {
|
||||
return nil, ErrPluginNotRegistered
|
||||
return nil, backendplugin.ErrPluginNotRegistered
|
||||
}
|
||||
|
||||
var resp *backend.CollectMetricsResult
|
||||
err := instrumentCollectMetrics(p.PluginID(), func() (innerErr error) {
|
||||
err := instrumentation.InstrumentCollectMetrics(p.PluginID(), func() (innerErr error) {
|
||||
resp, innerErr = p.CollectMetrics(ctx)
|
||||
return
|
||||
})
|
||||
@ -214,25 +205,25 @@ func (m *manager) CheckHealth(ctx context.Context, pluginContext backend.PluginC
|
||||
m.pluginsMu.RUnlock()
|
||||
|
||||
if !registered {
|
||||
return nil, ErrPluginNotRegistered
|
||||
return nil, backendplugin.ErrPluginNotRegistered
|
||||
}
|
||||
|
||||
var resp *backend.CheckHealthResult
|
||||
err = instrumentCheckHealthRequest(p.PluginID(), func() (innerErr error) {
|
||||
err = instrumentation.InstrumentCheckHealthRequest(p.PluginID(), func() (innerErr error) {
|
||||
resp, innerErr = p.CheckHealth(ctx, &backend.CheckHealthRequest{PluginContext: pluginContext})
|
||||
return
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrMethodNotImplemented) {
|
||||
if errors.Is(err, backendplugin.ErrMethodNotImplemented) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if errors.Is(err, ErrPluginUnavailable) {
|
||||
if errors.Is(err, backendplugin.ErrPluginUnavailable) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, errutil.Wrap("failed to check plugin health", ErrHealthCheckFailed)
|
||||
return nil, errutil.Wrap("failed to check plugin health", backendplugin.ErrHealthCheckFailed)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
@ -248,7 +239,7 @@ func (m *manager) callResourceInternal(w http.ResponseWriter, req *http.Request,
|
||||
m.pluginsMu.RUnlock()
|
||||
|
||||
if !registered {
|
||||
return ErrPluginNotRegistered
|
||||
return backendplugin.ErrPluginNotRegistered
|
||||
}
|
||||
|
||||
keepCookieModel := keepCookiesJSONModel{}
|
||||
@ -276,7 +267,7 @@ func (m *manager) callResourceInternal(w http.ResponseWriter, req *http.Request,
|
||||
Body: body,
|
||||
}
|
||||
|
||||
return instrumentCallResourceRequest(p.PluginID(), func() error {
|
||||
return instrumentation.InstrumentCallResourceRequest(p.PluginID(), func() error {
|
||||
childCtx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
stream := newCallResourceResponseStream(childCtx)
|
||||
@ -336,12 +327,12 @@ func (m *manager) CallResource(pCtx backend.PluginContext, reqCtx *models.ReqCon
|
||||
}
|
||||
|
||||
func handleCallResourceError(err error, reqCtx *models.ReqContext) {
|
||||
if errors.Is(err, ErrPluginUnavailable) {
|
||||
if errors.Is(err, backendplugin.ErrPluginUnavailable) {
|
||||
reqCtx.JsonApiErr(503, "Plugin unavailable", err)
|
||||
return
|
||||
}
|
||||
|
||||
if errors.Is(err, ErrMethodNotImplemented) {
|
||||
if errors.Is(err, backendplugin.ErrMethodNotImplemented) {
|
||||
reqCtx.JsonApiErr(404, "Not found", err)
|
||||
return
|
||||
}
|
||||
@ -349,7 +340,7 @@ func handleCallResourceError(err error, reqCtx *models.ReqContext) {
|
||||
reqCtx.JsonApiErr(500, "Failed to call resource", err)
|
||||
}
|
||||
|
||||
func flushStream(plugin Plugin, stream CallResourceClientResponseStream, w http.ResponseWriter) error {
|
||||
func flushStream(plugin backendplugin.Plugin, stream callResourceClientResponseStream, w http.ResponseWriter) error {
|
||||
processedStreams := 0
|
||||
|
||||
for {
|
||||
@ -404,12 +395,12 @@ func flushStream(plugin Plugin, stream CallResourceClientResponseStream, w http.
|
||||
}
|
||||
}
|
||||
|
||||
func startPluginAndRestartKilledProcesses(ctx context.Context, p Plugin) error {
|
||||
func startPluginAndRestartKilledProcesses(ctx context.Context, p backendplugin.Plugin) error {
|
||||
if err := p.Start(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go func(ctx context.Context, p Plugin) {
|
||||
go func(ctx context.Context, p backendplugin.Plugin) {
|
||||
if err := restartKilledProcess(ctx, p); err != nil {
|
||||
p.Logger().Error("Attempt to restart killed plugin process failed", "error", err)
|
||||
}
|
||||
@ -418,7 +409,7 @@ func startPluginAndRestartKilledProcesses(ctx context.Context, p Plugin) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func restartKilledProcess(ctx context.Context, p Plugin) error {
|
||||
func restartKilledProcess(ctx context.Context, p backendplugin.Plugin) error {
|
||||
ticker := time.NewTicker(time.Second * 1)
|
||||
|
||||
for {
|
||||
@ -442,3 +433,9 @@ func restartKilledProcess(ctx context.Context, p Plugin) error {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// callResourceClientResponseStream is used for receiving resource call responses.
|
||||
type callResourceClientResponseStream interface {
|
||||
Recv() (*backend.CallResourceResponse, error)
|
||||
Close() error
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package backendplugin
|
||||
package manager
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@ -12,6 +12,7 @@ import (
|
||||
"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/backendplugin"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@ -22,19 +23,19 @@ func TestManager(t *testing.T) {
|
||||
newManagerScenario(t, false, func(t *testing.T, ctx *managerScenarioCtx) {
|
||||
t.Run("Unregistered plugin scenario", func(t *testing.T) {
|
||||
err := ctx.manager.StartPlugin(context.Background(), testPluginID)
|
||||
require.Equal(t, ErrPluginNotRegistered, err)
|
||||
require.Equal(t, backendplugin.ErrPluginNotRegistered, err)
|
||||
|
||||
_, err = ctx.manager.CollectMetrics(context.Background(), testPluginID)
|
||||
require.Equal(t, ErrPluginNotRegistered, err)
|
||||
require.Equal(t, backendplugin.ErrPluginNotRegistered, err)
|
||||
|
||||
_, err = ctx.manager.CheckHealth(context.Background(), backend.PluginContext{PluginID: testPluginID})
|
||||
require.Equal(t, ErrPluginNotRegistered, err)
|
||||
require.Equal(t, backendplugin.ErrPluginNotRegistered, err)
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, "/test", nil)
|
||||
require.NoError(t, err)
|
||||
w := httptest.NewRecorder()
|
||||
err = ctx.manager.callResourceInternal(w, req, backend.PluginContext{PluginID: testPluginID})
|
||||
require.Equal(t, ErrPluginNotRegistered, err)
|
||||
require.Equal(t, backendplugin.ErrPluginNotRegistered, err)
|
||||
})
|
||||
})
|
||||
|
||||
@ -121,12 +122,12 @@ func TestManager(t *testing.T) {
|
||||
t.Run("Unimplemented handlers", func(t *testing.T) {
|
||||
t.Run("Collect metrics should return method not implemented error", func(t *testing.T) {
|
||||
_, err = ctx.manager.CollectMetrics(context.Background(), testPluginID)
|
||||
require.Equal(t, ErrMethodNotImplemented, err)
|
||||
require.Equal(t, backendplugin.ErrMethodNotImplemented, err)
|
||||
})
|
||||
|
||||
t.Run("Check health should return method not implemented error", func(t *testing.T) {
|
||||
_, err = ctx.manager.CheckHealth(context.Background(), backend.PluginContext{PluginID: testPluginID})
|
||||
require.Equal(t, ErrMethodNotImplemented, err)
|
||||
require.Equal(t, backendplugin.ErrMethodNotImplemented, err)
|
||||
})
|
||||
|
||||
t.Run("Call resource should return method not implemented error", func(t *testing.T) {
|
||||
@ -134,17 +135,17 @@ func TestManager(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
w := httptest.NewRecorder()
|
||||
err = ctx.manager.callResourceInternal(w, req, backend.PluginContext{PluginID: testPluginID})
|
||||
require.Equal(t, ErrMethodNotImplemented, err)
|
||||
require.Equal(t, backendplugin.ErrMethodNotImplemented, err)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Implemented handlers", func(t *testing.T) {
|
||||
t.Run("Collect metrics should return expected result", func(t *testing.T) {
|
||||
ctx.plugin.CollectMetricsHandlerFunc = backend.CollectMetricsHandlerFunc(func(ctx context.Context) (*backend.CollectMetricsResult, error) {
|
||||
ctx.plugin.CollectMetricsHandlerFunc = func(ctx context.Context) (*backend.CollectMetricsResult, error) {
|
||||
return &backend.CollectMetricsResult{
|
||||
PrometheusMetrics: []byte("hello"),
|
||||
}, nil
|
||||
})
|
||||
}
|
||||
|
||||
res, err := ctx.manager.CollectMetrics(context.Background(), testPluginID)
|
||||
require.NoError(t, err)
|
||||
@ -156,13 +157,13 @@ func TestManager(t *testing.T) {
|
||||
json := []byte(`{
|
||||
"key": "value"
|
||||
}`)
|
||||
ctx.plugin.CheckHealthHandlerFunc = backend.CheckHealthHandlerFunc(func(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
|
||||
ctx.plugin.CheckHealthHandlerFunc = func(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
|
||||
return &backend.CheckHealthResult{
|
||||
Status: backend.HealthStatusOk,
|
||||
Message: "All good",
|
||||
JSONDetails: json,
|
||||
}, nil
|
||||
})
|
||||
}
|
||||
|
||||
res, err := ctx.manager.CheckHealth(context.Background(), backend.PluginContext{PluginID: testPluginID})
|
||||
require.NoError(t, err)
|
||||
@ -173,11 +174,12 @@ func TestManager(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("Call resource should return expected response", func(t *testing.T) {
|
||||
ctx.plugin.CallResourceHandlerFunc = backend.CallResourceHandlerFunc(func(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
|
||||
ctx.plugin.CallResourceHandlerFunc = func(ctx context.Context,
|
||||
req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
|
||||
return sender.Send(&backend.CallResourceResponse{
|
||||
Status: http.StatusOK,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, "/test", bytes.NewReader([]byte{}))
|
||||
require.NoError(t, err)
|
||||
@ -270,7 +272,7 @@ type managerScenarioCtx struct {
|
||||
cfg *setting.Cfg
|
||||
license *testLicensingService
|
||||
manager *manager
|
||||
factory PluginFactoryFunc
|
||||
factory backendplugin.PluginFactoryFunc
|
||||
plugin *testPlugin
|
||||
env []string
|
||||
}
|
||||
@ -293,7 +295,7 @@ func newManagerScenario(t *testing.T, managed bool, fn func(t *testing.T, ctx *m
|
||||
err := ctx.manager.Init()
|
||||
require.NoError(t, err)
|
||||
|
||||
ctx.factory = PluginFactoryFunc(func(pluginID string, logger log.Logger, env []string) (Plugin, error) {
|
||||
ctx.factory = func(pluginID string, logger log.Logger, env []string) (backendplugin.Plugin, error) {
|
||||
ctx.plugin = &testPlugin{
|
||||
pluginID: pluginID,
|
||||
logger: logger,
|
||||
@ -302,7 +304,7 @@ func newManagerScenario(t *testing.T, managed bool, fn func(t *testing.T, ctx *m
|
||||
ctx.env = env
|
||||
|
||||
return ctx.plugin, nil
|
||||
})
|
||||
}
|
||||
|
||||
fn(t, ctx)
|
||||
}
|
||||
@ -328,6 +330,10 @@ func (tp *testPlugin) Logger() log.Logger {
|
||||
return tp.logger
|
||||
}
|
||||
|
||||
func (tp *testPlugin) CanHandleDataQueries() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (tp *testPlugin) Start(ctx context.Context) error {
|
||||
tp.mutex.Lock()
|
||||
defer tp.mutex.Unlock()
|
||||
@ -364,7 +370,7 @@ func (tp *testPlugin) CollectMetrics(ctx context.Context) (*backend.CollectMetri
|
||||
return tp.CollectMetricsHandlerFunc(ctx)
|
||||
}
|
||||
|
||||
return nil, ErrMethodNotImplemented
|
||||
return nil, backendplugin.ErrMethodNotImplemented
|
||||
}
|
||||
|
||||
func (tp *testPlugin) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
|
||||
@ -372,7 +378,7 @@ func (tp *testPlugin) CheckHealth(ctx context.Context, req *backend.CheckHealthR
|
||||
return tp.CheckHealthHandlerFunc(ctx, req)
|
||||
}
|
||||
|
||||
return nil, ErrMethodNotImplemented
|
||||
return nil, backendplugin.ErrMethodNotImplemented
|
||||
}
|
||||
|
||||
func (tp *testPlugin) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
|
||||
@ -380,7 +386,7 @@ func (tp *testPlugin) CallResource(ctx context.Context, req *backend.CallResourc
|
||||
return tp.CallResourceHandlerFunc(ctx, req, sender)
|
||||
}
|
||||
|
||||
return ErrMethodNotImplemented
|
||||
return backendplugin.ErrMethodNotImplemented
|
||||
}
|
||||
|
||||
type testLicensingService struct {
|
@ -1,4 +1,4 @@
|
||||
package backendplugin
|
||||
package manager
|
||||
|
||||
import (
|
||||
"fmt"
|
@ -1,4 +1,4 @@
|
||||
package backendplugin
|
||||
package manager
|
||||
|
||||
import (
|
||||
"sort"
|
@ -1,4 +1,4 @@
|
||||
package backendplugin
|
||||
package manager
|
||||
|
||||
import (
|
||||
"context"
|
@ -1,30 +0,0 @@
|
||||
package backendplugin
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
)
|
||||
|
||||
// Plugin backend plugin interface.
|
||||
type Plugin interface {
|
||||
PluginID() string
|
||||
Logger() log.Logger
|
||||
Start(ctx context.Context) error
|
||||
Stop(ctx context.Context) error
|
||||
IsManaged() bool
|
||||
Exited() bool
|
||||
backend.CollectMetricsHandler
|
||||
backend.CheckHealthHandler
|
||||
backend.CallResourceHandler
|
||||
}
|
||||
|
||||
// PluginFactoryFunc factory for creating a Plugin.
|
||||
type PluginFactoryFunc func(pluginID string, logger log.Logger, env []string) (Plugin, error)
|
||||
|
||||
// CallResourceClientResponseStream is used for receiving resource call responses.
|
||||
type CallResourceClientResponseStream interface {
|
||||
Recv() (*backend.CallResourceResponse, error)
|
||||
Close() error
|
||||
}
|
Reference in New Issue
Block a user