The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/pkg/plugins/backendplugin/backend_plugin.go

238 lines
5.8 KiB

package backendplugin
import (
"context"
"errors"
"net/http"
datasourceV1 "github.com/grafana/grafana-plugin-model/go/datasource"
rendererV1 "github.com/grafana/grafana-plugin-model/go/renderer"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/genproto/pluginv2"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/plugins/backendplugin/pluginextensionv2"
"github.com/grafana/grafana/pkg/util/errutil"
plugin "github.com/hashicorp/go-plugin"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
// BackendPlugin a registered backend plugin.
type BackendPlugin struct {
id string
executablePath string
managed bool
clientFactory func() *plugin.Client
client *plugin.Client
logger log.Logger
startFns PluginStartFuncs
diagnostics DiagnosticsPlugin
resource ResourcePlugin
}
func (p *BackendPlugin) start(ctx context.Context) error {
p.client = p.clientFactory()
rpcClient, err := p.client.Client()
if err != nil {
return err
}
var legacyClient *LegacyClient
var client *Client
if p.client.NegotiatedVersion() > 1 {
rawDiagnostics, err := rpcClient.Dispense("diagnostics")
if err != nil {
return err
}
rawResource, err := rpcClient.Dispense("resource")
if err != nil {
return err
}
rawData, err := rpcClient.Dispense("data")
if err != nil {
return err
}
rawTransform, err := rpcClient.Dispense("transform")
if err != nil {
return err
}
rawRenderer, err := rpcClient.Dispense("renderer")
if err != nil {
return err
}
if rawDiagnostics != nil {
if plugin, ok := rawDiagnostics.(DiagnosticsPlugin); ok {
p.diagnostics = plugin
}
}
client = &Client{}
if rawResource != nil {
if plugin, ok := rawResource.(ResourcePlugin); ok {
p.resource = plugin
client.ResourcePlugin = plugin
}
}
if rawData != nil {
if plugin, ok := rawData.(DataPlugin); ok {
client.DataPlugin = plugin
}
}
if rawTransform != nil {
if plugin, ok := rawTransform.(TransformPlugin); ok {
client.TransformPlugin = plugin
}
}
if rawRenderer != nil {
if plugin, ok := rawRenderer.(pluginextensionv2.RendererPlugin); ok {
client.RendererPlugin = plugin
}
}
} else {
raw, err := rpcClient.Dispense(p.id)
if err != nil {
return err
}
legacyClient = &LegacyClient{}
if plugin, ok := raw.(datasourceV1.DatasourcePlugin); ok {
legacyClient.DatasourcePlugin = plugin
}
if plugin, ok := raw.(rendererV1.RendererPlugin); ok {
legacyClient.RendererPlugin = plugin
}
}
if legacyClient == nil && client == nil {
return errors.New("no compatible plugin implementation found")
}
if legacyClient != nil && p.startFns.OnLegacyStart != nil {
if err := p.startFns.OnLegacyStart(p.id, legacyClient, p.logger); err != nil {
return err
}
}
if client != nil && p.startFns.OnStart != nil {
if err := p.startFns.OnStart(p.id, client, p.logger); err != nil {
return err
}
}
return nil
}
func (p *BackendPlugin) stop() error {
if p.client != nil {
p.client.Kill()
}
return nil
}
// supportsDiagnostics return whether backend plugin supports diagnostics like metrics and health check.
func (p *BackendPlugin) supportsDiagnostics() bool {
return p.diagnostics != nil
}
// CollectMetrics implements the collector.Collector interface.
func (p *BackendPlugin) CollectMetrics(ctx context.Context) (*pluginv2.CollectMetricsResponse, error) {
if p.diagnostics == nil || p.client == nil || p.client.Exited() {
return &pluginv2.CollectMetricsResponse{
Metrics: &pluginv2.CollectMetricsResponse_Payload{},
}, nil
}
var res *pluginv2.CollectMetricsResponse
err := InstrumentPluginRequest(p.id, "metrics", func() error {
var innerErr error
res, innerErr = p.diagnostics.CollectMetrics(ctx, &pluginv2.CollectMetricsRequest{})
return innerErr
})
if err != nil {
if st, ok := status.FromError(err); ok {
if st.Code() == codes.Unimplemented {
return &pluginv2.CollectMetricsResponse{
Metrics: &pluginv2.CollectMetricsResponse_Payload{},
}, nil
}
}
return nil, err
}
return res, nil
}
var toProto = backend.ToProto()
func (p *BackendPlugin) checkHealth(ctx context.Context, pCtx backend.PluginContext) (*pluginv2.CheckHealthResponse, error) {
if p.diagnostics == nil || p.client == nil || p.client.Exited() {
return &pluginv2.CheckHealthResponse{
Status: pluginv2.CheckHealthResponse_UNKNOWN,
}, nil
}
protoContext := toProto.PluginContext(pCtx)
var res *pluginv2.CheckHealthResponse
err := InstrumentPluginRequest(p.id, "checkhealth", func() error {
var innerErr error
res, innerErr = p.diagnostics.CheckHealth(ctx, &pluginv2.CheckHealthRequest{PluginContext: protoContext})
return innerErr
})
if err != nil {
if st, ok := status.FromError(err); ok {
if st.Code() == codes.Unimplemented {
return &pluginv2.CheckHealthResponse{
Status: pluginv2.CheckHealthResponse_UNKNOWN,
Message: "Health check not implemented",
}, nil
}
}
return nil, err
}
return res, nil
}
func (p *BackendPlugin) callResource(ctx context.Context, req *backend.CallResourceRequest) (callResourceResultStream, error) {
p.logger.Debug("Calling resource", "path", req.Path, "method", req.Method)
if p.resource == nil || p.client == nil || p.client.Exited() {
return nil, errors.New("plugin not running, cannot call resource")
}
protoReq := toProto.CallResourceRequest(req)
protoStream, err := p.resource.CallResource(ctx, protoReq)
if err != nil {
if st, ok := status.FromError(err); ok {
if st.Code() == codes.Unimplemented {
return &singleCallResourceResult{
result: &CallResourceResult{
Status: http.StatusNotImplemented,
},
}, nil
}
}
return nil, errutil.Wrap("Failed to call resource", err)
}
return &callResourceResultStreamImpl{
stream: protoStream,
}, nil
}