Backend plugins: Refactor to allow shared contract between core and external backend plugins (#25472)
Refactor to allow shared contract between core and external backend plugins allowing core backend data sources in Grafana to be implemented in same way as an external backend plugin. Use v0.67.0 of sdk. Add tests for verifying plugin is restarted when process is killed. Enable strict linting for backendplugin packages
This commit is contained in:
committed by
GitHub
parent
40b3473a10
commit
c0f3b2929c
@@ -0,0 +1,212 @@
|
||||
package grpcplugin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"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/infra/log"
|
||||
"github.com/grafana/grafana/pkg/plugins/backendplugin"
|
||||
"github.com/grafana/grafana/pkg/plugins/backendplugin/pluginextensionv2"
|
||||
"github.com/grafana/grafana/pkg/util/errutil"
|
||||
"github.com/hashicorp/go-plugin"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
type clientV2 struct {
|
||||
grpcplugin.DiagnosticsClient
|
||||
grpcplugin.ResourceClient
|
||||
grpcplugin.DataClient
|
||||
grpcplugin.TransformClient
|
||||
pluginextensionv2.RendererPlugin
|
||||
}
|
||||
|
||||
func newClientV2(descriptor PluginDescriptor, logger log.Logger, rpcClient plugin.ClientProtocol) (pluginClient, error) {
|
||||
rawDiagnostics, err := rpcClient.Dispense("diagnostics")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rawResource, err := rpcClient.Dispense("resource")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rawData, err := rpcClient.Dispense("data")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rawTransform, err := rpcClient.Dispense("transform")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rawRenderer, err := rpcClient.Dispense("renderer")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c := clientV2{}
|
||||
if rawDiagnostics != nil {
|
||||
if plugin, ok := rawDiagnostics.(grpcplugin.DiagnosticsClient); ok {
|
||||
c.DiagnosticsClient = plugin
|
||||
}
|
||||
}
|
||||
|
||||
if rawResource != nil {
|
||||
if plugin, ok := rawResource.(grpcplugin.ResourceClient); ok {
|
||||
c.ResourceClient = plugin
|
||||
}
|
||||
}
|
||||
|
||||
if rawData != nil {
|
||||
if plugin, ok := rawData.(grpcplugin.DataClient); ok {
|
||||
c.DataClient = instrumentDataClient(plugin)
|
||||
}
|
||||
}
|
||||
|
||||
if rawTransform != nil {
|
||||
if plugin, ok := rawTransform.(grpcplugin.TransformClient); ok {
|
||||
c.TransformClient = instrumentTransformPlugin(plugin)
|
||||
}
|
||||
}
|
||||
|
||||
if rawRenderer != nil {
|
||||
if plugin, ok := rawRenderer.(pluginextensionv2.RendererPlugin); ok {
|
||||
c.RendererPlugin = plugin
|
||||
}
|
||||
}
|
||||
|
||||
if descriptor.startFns.OnStart != nil {
|
||||
client := &Client{
|
||||
DataPlugin: c.DataClient,
|
||||
TransformPlugin: c.TransformClient,
|
||||
RendererPlugin: c.RendererPlugin,
|
||||
}
|
||||
if err := descriptor.startFns.OnStart(descriptor.pluginID, client, logger); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &c, nil
|
||||
}
|
||||
|
||||
func (c *clientV2) CollectMetrics(ctx context.Context) (*backend.CollectMetricsResult, error) {
|
||||
if c.DiagnosticsClient == nil {
|
||||
return &backend.CollectMetricsResult{}, nil
|
||||
}
|
||||
|
||||
protoResp, err := c.DiagnosticsClient.CollectMetrics(ctx, &pluginv2.CollectMetricsRequest{})
|
||||
if err != nil {
|
||||
if status.Code(err) == codes.Unimplemented {
|
||||
return &backend.CollectMetricsResult{}, nil
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return backend.FromProto().CollectMetricsResponse(protoResp), nil
|
||||
}
|
||||
|
||||
func (c *clientV2) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
|
||||
if c.DiagnosticsClient == nil {
|
||||
return nil, backendplugin.ErrMethodNotImplemented
|
||||
}
|
||||
|
||||
protoContext := backend.ToProto().PluginContext(req.PluginContext)
|
||||
protoResp, err := c.DiagnosticsClient.CheckHealth(ctx, &pluginv2.CheckHealthRequest{PluginContext: protoContext})
|
||||
|
||||
if err != nil {
|
||||
if status.Code(err) == codes.Unimplemented {
|
||||
return &backend.CheckHealthResult{
|
||||
Status: backend.HealthStatusUnknown,
|
||||
Message: "Health check not implemented",
|
||||
}, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return backend.FromProto().CheckHealthResponse(protoResp), nil
|
||||
}
|
||||
|
||||
func (c *clientV2) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
|
||||
if c.ResourceClient == nil {
|
||||
return backendplugin.ErrMethodNotImplemented
|
||||
}
|
||||
|
||||
protoReq := backend.ToProto().CallResourceRequest(req)
|
||||
protoStream, err := c.ResourceClient.CallResource(ctx, protoReq)
|
||||
if err != nil {
|
||||
if status.Code(err) == codes.Unimplemented {
|
||||
return backendplugin.ErrMethodNotImplemented
|
||||
}
|
||||
|
||||
return errutil.Wrap("Failed to call resource", err)
|
||||
}
|
||||
|
||||
for {
|
||||
protoResp, err := protoStream.Recv()
|
||||
if err != nil {
|
||||
if status.Code(err) == codes.Unimplemented {
|
||||
return backendplugin.ErrMethodNotImplemented
|
||||
}
|
||||
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
|
||||
return errutil.Wrap("Failed to receive call resource response", err)
|
||||
}
|
||||
|
||||
if err := sender.Send(backend.FromProto().CallResourceResponse(protoResp)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 := backendplugin.InstrumentQueryDataRequest(req.PluginContext.PluginId, func() (innerErr error) {
|
||||
resp, innerErr = plugin.QueryData(ctx, req)
|
||||
return
|
||||
})
|
||||
return resp, err
|
||||
})
|
||||
}
|
||||
|
||||
type transformPluginTransformDataFunc func(ctx context.Context, req *pluginv2.QueryDataRequest, callback grpcplugin.TransformDataCallBack) (*pluginv2.QueryDataResponse, error)
|
||||
|
||||
func (fn transformPluginTransformDataFunc) TransformData(ctx context.Context, req *pluginv2.QueryDataRequest, callback grpcplugin.TransformDataCallBack) (*pluginv2.QueryDataResponse, error) {
|
||||
return fn(ctx, req, callback)
|
||||
}
|
||||
|
||||
func instrumentTransformPlugin(plugin grpcplugin.TransformClient) grpcplugin.TransformClient {
|
||||
if plugin == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return transformPluginTransformDataFunc(func(ctx context.Context, req *pluginv2.QueryDataRequest, callback grpcplugin.TransformDataCallBack) (*pluginv2.QueryDataResponse, error) {
|
||||
var resp *pluginv2.QueryDataResponse
|
||||
err := backendplugin.InstrumentTransformDataRequest(req.PluginContext.PluginId, func() (innerErr error) {
|
||||
resp, innerErr = plugin.TransformData(ctx, req, callback)
|
||||
return
|
||||
})
|
||||
return resp, err
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user