Plugins: Use grafana-plugin-sdk-go v0.5.0 (#21116)

* fix dispense

* plugin loading refactor

Moves common functionality of registering, starting,
stopping and restarting backend plugins to
backendplugin package.

* simplify plugin descriptors

* target sdk v0.5.0

Co-authored-by: Kyle Brandt <kyle@kbrandt.com>
This commit is contained in:
Marcus Efraimsson
2020-01-08 17:43:28 +01:00
committed by Kyle Brandt
parent 5321e7536f
commit baba1634b8
306 changed files with 81274 additions and 41083 deletions

View File

@ -7,9 +7,8 @@ import (
datasourceV1 "github.com/grafana/grafana-plugin-model/go/datasource"
rendererV1 "github.com/grafana/grafana-plugin-model/go/renderer"
backend "github.com/grafana/grafana-plugin-sdk-go/backend"
sdk "github.com/grafana/grafana-plugin-sdk-go/common"
datasourceV2 "github.com/grafana/grafana-plugin-sdk-go/datasource"
transformV2 "github.com/grafana/grafana-plugin-sdk-go/transform"
"github.com/hashicorp/go-plugin"
)
@ -32,9 +31,7 @@ var handshake = plugin.HandshakeConfig{
MagicCookieValue: sdk.MagicCookieValue,
}
// NewClientConfig returns a configuration object that can be used to instantiate
// a client for the plugin described by the given metadata.
func NewClientConfig(executablePath string, logger log.Logger, versionedPlugins map[int]plugin.PluginSet) *plugin.ClientConfig {
func newClientConfig(executablePath string, logger log.Logger, versionedPlugins map[int]plugin.PluginSet) *plugin.ClientConfig {
return &plugin.ClientConfig{
Cmd: exec.Command(executablePath),
HandshakeConfig: handshake,
@ -44,38 +41,44 @@ func NewClientConfig(executablePath string, logger log.Logger, versionedPlugins
}
}
// NewDatasourceClient returns a datasource plugin client.
func NewDatasourceClient(pluginID, executablePath string, logger log.Logger) *plugin.Client {
versionedPlugins := map[int]plugin.PluginSet{
1: {
pluginID: &datasourceV1.DatasourcePluginImpl{},
},
2: {
pluginID: &datasourceV2.DatasourcePluginImpl{},
},
}
return plugin.NewClient(NewClientConfig(executablePath, logger, versionedPlugins))
// PluginDescriptor descriptor used for registering backend plugins.
type PluginDescriptor struct {
pluginID string
executablePath string
managed bool
versionedPlugins map[int]plugin.PluginSet
}
// NewRendererClient returns a renderer plugin client.
func NewRendererClient(pluginID, executablePath string, logger log.Logger) *plugin.Client {
versionedPlugins := map[int]plugin.PluginSet{
1: {
pluginID: &rendererV1.RendererPluginImpl{},
// NewBackendPluginDescriptor creates a new backend plugin descriptor
// used for registering a backend datasource plugin.
func NewBackendPluginDescriptor(pluginID, executablePath string) PluginDescriptor {
return PluginDescriptor{
pluginID: pluginID,
executablePath: executablePath,
managed: true,
versionedPlugins: map[int]plugin.PluginSet{
DefaultProtocolVersion: {
pluginID: &datasourceV1.DatasourcePluginImpl{},
},
sdk.ProtocolVersion: {
"backend": &backend.CoreGRPCPlugin{},
"transform": &backend.TransformGRPCPlugin{},
},
},
}
return plugin.NewClient(NewClientConfig(executablePath, logger, versionedPlugins))
}
// NewTransformClient returns a transform plugin client.
func NewTransformClient(pluginID, executablePath string, logger log.Logger) *plugin.Client {
versionedPlugins := map[int]plugin.PluginSet{
2: {
pluginID: &transformV2.TransformPluginImpl{},
// NewRendererPluginDescriptor creates a new renderer plugin descriptor
// used for registering a backend renderer plugin.
func NewRendererPluginDescriptor(pluginID, executablePath string) PluginDescriptor {
return PluginDescriptor{
pluginID: pluginID,
executablePath: executablePath,
managed: false,
versionedPlugins: map[int]plugin.PluginSet{
DefaultProtocolVersion: {
pluginID: &rendererV1.RendererPluginImpl{},
},
},
}
return plugin.NewClient(NewClientConfig(executablePath, logger, versionedPlugins))
}

View File

@ -0,0 +1,197 @@
package backendplugin
import (
"context"
"errors"
"sync"
"time"
"github.com/grafana/grafana/pkg/infra/log"
plugin "github.com/hashicorp/go-plugin"
"golang.org/x/xerrors"
)
var (
pluginsMu sync.RWMutex
plugins = make(map[string]*BackendPlugin)
logger = log.New("plugins.backend")
)
type BackendPluginCallbackFunc func(pluginID string, client *plugin.Client, logger log.Logger) error
type BackendPlugin struct {
id string
executablePath string
managed bool
clientFactory func() *plugin.Client
client *plugin.Client
logger log.Logger
callbackFn BackendPluginCallbackFunc
supportsMetrics bool
supportsHealth bool
}
func (p *BackendPlugin) start(ctx context.Context) error {
p.client = p.clientFactory()
// rpcClient, err := p.client.Client()
// if err != nil {
// return err
// }
// if p.client.NegotiatedVersion() > 1 {
// _, err = rpcClient.Dispense("diagnostics")
// if err != nil {
// return err
// }
// }
if p.callbackFn != nil {
return p.callbackFn(p.id, p.client, p.logger)
}
return nil
}
func (p *BackendPlugin) stop() error {
if p.client != nil {
p.client.Kill()
}
return nil
}
func (p *BackendPlugin) collectMetrics(ctx context.Context) {
if !p.supportsMetrics {
return
}
}
func (p *BackendPlugin) checkHealth(ctx context.Context) {
if !p.supportsHealth {
return
}
}
// Register registers a backend plugin
func Register(descriptor PluginDescriptor, callbackFn BackendPluginCallbackFunc) error {
logger.Debug("Registering backend plugin", "pluginId", descriptor.pluginID, "executablePath", descriptor.executablePath)
pluginsMu.Lock()
defer pluginsMu.Unlock()
if _, exists := plugins[descriptor.pluginID]; exists {
return errors.New("Backend plugin already registered")
}
pluginLogger := logger.New("pluginId", descriptor.pluginID)
plugin := &BackendPlugin{
id: descriptor.pluginID,
executablePath: descriptor.executablePath,
managed: descriptor.managed,
clientFactory: func() *plugin.Client {
return plugin.NewClient(newClientConfig(descriptor.executablePath, pluginLogger, descriptor.versionedPlugins))
},
callbackFn: callbackFn,
logger: pluginLogger,
}
plugins[descriptor.pluginID] = plugin
logger.Debug("Backend plugin registered", "pluginId", descriptor.pluginID, "executablePath", descriptor.executablePath)
return nil
}
// Start starts all managed backend plugins
func Start(ctx context.Context) {
pluginsMu.RLock()
defer pluginsMu.RUnlock()
for _, p := range plugins {
if !p.managed {
continue
}
if err := startPluginAndRestartKilledProcesses(ctx, p); err != nil {
p.logger.Error("Failed to start plugin", "error", err)
}
}
}
// StartPlugin starts a non-managed backend plugin
func StartPlugin(ctx context.Context, pluginID string) error {
pluginsMu.RLock()
p, registered := plugins[pluginID]
pluginsMu.RUnlock()
if !registered {
return errors.New("Backend plugin not registered")
}
if p.managed {
return errors.New("Backend plugin is managed and cannot be manually started")
}
return startPluginAndRestartKilledProcesses(ctx, p)
}
func startPluginAndRestartKilledProcesses(ctx context.Context, p *BackendPlugin) error {
if err := p.start(ctx); err != nil {
return err
}
go func(ctx context.Context, p *BackendPlugin) {
if err := restartKilledProcess(ctx, p); err != nil {
p.logger.Error("Attempt to restart killed plugin process failed", "error", err)
}
}(ctx, p)
return nil
}
// Stop stops all managed backend plugins
func Stop() {
pluginsMu.RLock()
defer pluginsMu.RUnlock()
for _, p := range plugins {
go func(p *BackendPlugin) {
p.logger.Debug("Stopping plugin")
if err := p.stop(); err != nil {
p.logger.Error("Failed to stop plugin", "error", err)
}
p.logger.Debug("Plugin stopped")
}(p)
}
}
// CollectMetrics collect metrics from backend plugins
func CollectMetrics(ctx context.Context) {
for _, p := range plugins {
p.collectMetrics(ctx)
}
}
// CheckHealth checks health of backend plugins
func CheckHealth(ctx context.Context) {
for _, p := range plugins {
p.checkHealth(ctx)
}
}
func restartKilledProcess(ctx context.Context, p *BackendPlugin) error {
ticker := time.NewTicker(time.Second * 1)
for {
select {
case <-ctx.Done():
if err := ctx.Err(); err != nil && !xerrors.Is(err, context.Canceled) {
return err
}
return nil
case <-ticker.C:
if !p.client.Exited() {
continue
}
p.logger.Debug("Restarting plugin")
if err := p.start(ctx); err != nil {
p.logger.Error("Failed to restart plugin", "error", err)
continue
}
p.logger.Debug("Plugin restarted")
}
}
}