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:
Marcus Efraimsson
2020-06-11 16:14:05 +02:00
committed by GitHub
parent 40b3473a10
commit c0f3b2929c
29 changed files with 1495 additions and 612 deletions

View File

@ -0,0 +1,136 @@
package grpcplugin
import (
"context"
"errors"
"sync"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/plugins/backendplugin"
"github.com/hashicorp/go-plugin"
)
type pluginClient interface {
backend.CollectMetricsHandler
backend.CheckHealthHandler
backend.CallResourceHandler
}
type grpcPlugin struct {
descriptor PluginDescriptor
clientFactory func() *plugin.Client
client *plugin.Client
pluginClient pluginClient
logger log.Logger
mutex sync.RWMutex
}
// New allocates and returns a new gRPC (external) backendplugin.Plugin.
func New(descriptor PluginDescriptor) backendplugin.PluginFactoryFunc {
return backendplugin.PluginFactoryFunc(func(pluginID string, logger log.Logger, env []string) (backendplugin.Plugin, error) {
return &grpcPlugin{
descriptor: descriptor,
logger: logger,
clientFactory: func() *plugin.Client {
return plugin.NewClient(newClientConfig(descriptor.executablePath, env, logger, descriptor.versionedPlugins))
},
}, nil
})
}
func (p *grpcPlugin) PluginID() string {
return p.descriptor.pluginID
}
func (p *grpcPlugin) Logger() log.Logger {
return p.logger
}
func (p *grpcPlugin) Start(ctx context.Context) error {
p.mutex.Lock()
defer p.mutex.Unlock()
p.client = p.clientFactory()
rpcClient, err := p.client.Client()
if err != nil {
return err
}
if p.client.NegotiatedVersion() > 1 {
p.pluginClient, err = newClientV2(p.descriptor, p.logger, rpcClient)
if err != nil {
return err
}
} else {
p.pluginClient, err = newClientV1(p.descriptor, p.logger, rpcClient)
if err != nil {
return err
}
}
if p.pluginClient == nil {
return errors.New("no compatible plugin implementation found")
}
return nil
}
func (p *grpcPlugin) Stop(ctx context.Context) error {
p.mutex.Lock()
defer p.mutex.Unlock()
if p.client != nil {
p.client.Kill()
}
return nil
}
func (p *grpcPlugin) IsManaged() bool {
return p.descriptor.managed
}
func (p *grpcPlugin) Exited() bool {
p.mutex.RLock()
defer p.mutex.RUnlock()
if p.client != nil {
return p.client.Exited()
}
return true
}
func (p *grpcPlugin) CollectMetrics(ctx context.Context) (*backend.CollectMetricsResult, error) {
p.mutex.RLock()
if p.client == nil || p.client.Exited() || p.pluginClient == nil {
p.mutex.RUnlock()
return nil, backendplugin.ErrPluginUnavailable
}
pluginClient := p.pluginClient
p.mutex.RUnlock()
return pluginClient.CollectMetrics(ctx)
}
func (p *grpcPlugin) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
p.mutex.RLock()
if p.client == nil || p.client.Exited() || p.pluginClient == nil {
p.mutex.RUnlock()
return nil, backendplugin.ErrPluginUnavailable
}
pluginClient := p.pluginClient
p.mutex.RUnlock()
return pluginClient.CheckHealth(ctx, req)
}
func (p *grpcPlugin) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
p.mutex.RLock()
if p.client == nil || p.client.Exited() || p.pluginClient == nil {
p.mutex.RUnlock()
return backendplugin.ErrPluginUnavailable
}
pluginClient := p.pluginClient
p.mutex.RUnlock()
return pluginClient.CallResource(ctx, req, sender)
}