kill plugin processes when grafana shuts down
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
package plugins
|
||||
package backend
|
||||
|
||||
import (
|
||||
"log"
|
||||
@@ -7,32 +7,32 @@ import (
|
||||
hclog "github.com/hashicorp/go-hclog"
|
||||
)
|
||||
|
||||
type logWrapper struct {
|
||||
logger glog.Logger
|
||||
type LogWrapper struct {
|
||||
Logger glog.Logger
|
||||
}
|
||||
|
||||
func (lw logWrapper) Trace(msg string, args ...interface{}) {}
|
||||
func (lw logWrapper) Debug(msg string, args ...interface{}) {}
|
||||
func (lw logWrapper) Info(msg string, args ...interface{}) {}
|
||||
func (lw logWrapper) Warn(msg string, args ...interface{}) {}
|
||||
func (lw logWrapper) Error(msg string, args ...interface{}) {}
|
||||
func (lw LogWrapper) Trace(msg string, args ...interface{}) {}
|
||||
func (lw LogWrapper) Debug(msg string, args ...interface{}) {}
|
||||
func (lw LogWrapper) Info(msg string, args ...interface{}) {}
|
||||
func (lw LogWrapper) Warn(msg string, args ...interface{}) {}
|
||||
func (lw LogWrapper) Error(msg string, args ...interface{}) {}
|
||||
|
||||
func (lw logWrapper) IsTrace() bool { return true }
|
||||
func (lw logWrapper) IsDebug() bool { return true }
|
||||
func (lw logWrapper) IsInfo() bool { return true }
|
||||
func (lw logWrapper) IsWarn() bool { return true }
|
||||
func (lw logWrapper) IsError() bool { return true }
|
||||
func (lw LogWrapper) IsTrace() bool { return true }
|
||||
func (lw LogWrapper) IsDebug() bool { return true }
|
||||
func (lw LogWrapper) IsInfo() bool { return true }
|
||||
func (lw LogWrapper) IsWarn() bool { return true }
|
||||
func (lw LogWrapper) IsError() bool { return true }
|
||||
|
||||
func (lw logWrapper) With(args ...interface{}) hclog.Logger {
|
||||
return logWrapper{logger: glog.New("logger", args)}
|
||||
func (lw LogWrapper) With(args ...interface{}) hclog.Logger {
|
||||
return LogWrapper{Logger: glog.New("logger", args)}
|
||||
}
|
||||
func (lw logWrapper) Named(name string) hclog.Logger {
|
||||
return logWrapper{logger: glog.New(name)}
|
||||
func (lw LogWrapper) Named(name string) hclog.Logger {
|
||||
return LogWrapper{Logger: glog.New(name)}
|
||||
}
|
||||
func (lw logWrapper) ResetNamed(name string) hclog.Logger {
|
||||
return logWrapper{logger: glog.New(name)}
|
||||
func (lw LogWrapper) ResetNamed(name string) hclog.Logger {
|
||||
return LogWrapper{Logger: glog.New(name)}
|
||||
}
|
||||
|
||||
func (lw logWrapper) StandardLogger(ops *hclog.StandardLoggerOptions) *log.Logger {
|
||||
func (lw LogWrapper) StandardLogger(ops *hclog.StandardLoggerOptions) *log.Logger {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
all: build
|
||||
|
||||
build:
|
||||
go build -o simple-plugin .
|
||||
@@ -1,39 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"log"
|
||||
|
||||
shared "github.com/grafana/grafana/pkg/plugins/backend/shared"
|
||||
proto "github.com/grafana/grafana/pkg/tsdb/models"
|
||||
plugin "github.com/hashicorp/go-plugin"
|
||||
)
|
||||
|
||||
type Tsdb struct {
|
||||
plugin.NetRPCUnsupportedPlugin
|
||||
}
|
||||
|
||||
func (Tsdb) Get(ctx context.Context, req *proto.TsdbRequest) (*proto.TsdbResponse, error) {
|
||||
log.Print("Tsdb.Get() from plugin")
|
||||
|
||||
return &proto.TsdbResponse{
|
||||
MetaJson: "from plugins! meta meta",
|
||||
}, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
plugin.Serve(&plugin.ServeConfig{
|
||||
HandshakeConfig: plugin.HandshakeConfig{
|
||||
ProtocolVersion: 1,
|
||||
MagicCookieKey: "BASIC_PLUGIN",
|
||||
MagicCookieValue: "hello",
|
||||
},
|
||||
Plugins: map[string]plugin.Plugin{
|
||||
"tsdb_mock": &shared.TsdbPluginImpl{Plugin: &Tsdb{}},
|
||||
},
|
||||
|
||||
// A non-nil value here enables gRPC serving for this plugin...
|
||||
GRPCServer: plugin.DefaultGRPCServer,
|
||||
})
|
||||
}
|
||||
Binary file not shown.
@@ -1,55 +0,0 @@
|
||||
package plugins
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/grafana/grafana/pkg/log"
|
||||
"github.com/grafana/grafana/pkg/plugins/backend/shared"
|
||||
"github.com/grafana/grafana/pkg/tsdb/models"
|
||||
plugin "github.com/hashicorp/go-plugin"
|
||||
)
|
||||
|
||||
func Init() (*plugin.Client, error) {
|
||||
/*
|
||||
setup protoc using https://gist.github.com/bergquist/5df1f201bb605e42538ef40f6ccf82a9
|
||||
run "protoc --go_out=plugins=grpc:. *.proto" to update proto files
|
||||
*/
|
||||
|
||||
logger := log.New("grafana.plugins")
|
||||
client := plugin.NewClient(&plugin.ClientConfig{
|
||||
HandshakeConfig: plugin.HandshakeConfig{
|
||||
ProtocolVersion: 1,
|
||||
MagicCookieKey: "BASIC_PLUGIN",
|
||||
MagicCookieValue: "hello",
|
||||
},
|
||||
Plugins: shared.PluginMap,
|
||||
Cmd: exec.Command("sh", "-c", "/home/carl/go/src/github.com/grafana/grafana/pkg/plugins/backend/mock_tsdb_plugin/simple-plugin"),
|
||||
AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC},
|
||||
Logger: logWrapper{logger: logger},
|
||||
})
|
||||
|
||||
// Connect via RPC
|
||||
rpcClient, err := client.Client()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Request the plugin
|
||||
raw, err := rpcClient.Dispense("tsdb_mock")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
plugin := raw.(shared.TsdbPlugin)
|
||||
response, err := plugin.Get(context.Background(), &proto.TsdbRequest{})
|
||||
|
||||
if err != nil {
|
||||
logger.Error("Response from plugin. ", "response", response)
|
||||
} else {
|
||||
logger.Info("Response from plugin. ", "response", response)
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package shared
|
||||
|
||||
import (
|
||||
proto "github.com/grafana/grafana/pkg/tsdb/models"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type GRPCClient struct {
|
||||
proto.TsdbPluginClient
|
||||
}
|
||||
|
||||
func (m *GRPCClient) Get(ctx context.Context, req *proto.TsdbRequest) (*proto.TsdbResponse, error) {
|
||||
return m.TsdbPluginClient.Get(ctx, req)
|
||||
}
|
||||
|
||||
type GRPCServer struct {
|
||||
TsdbPlugin
|
||||
}
|
||||
|
||||
func (m *GRPCServer) Get(ctx context.Context, req *proto.TsdbRequest) (*proto.TsdbResponse, error) {
|
||||
return m.TsdbPlugin.Get(ctx, req)
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package shared
|
||||
|
||||
import (
|
||||
"golang.org/x/net/context"
|
||||
|
||||
proto "github.com/grafana/grafana/pkg/tsdb/models"
|
||||
plugin "github.com/hashicorp/go-plugin"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
var PluginMap = map[string]plugin.Plugin{
|
||||
"tsdb_mock": &TsdbPluginImpl{},
|
||||
}
|
||||
|
||||
type TsdbPlugin interface {
|
||||
Get(ctx context.Context, req *proto.TsdbRequest) (*proto.TsdbResponse, error)
|
||||
}
|
||||
|
||||
type TsdbPluginImpl struct { //LOL IMPL LOL
|
||||
plugin.NetRPCUnsupportedPlugin
|
||||
Plugin TsdbPlugin
|
||||
}
|
||||
|
||||
func (p *TsdbPluginImpl) GRPCServer(s *grpc.Server) error {
|
||||
proto.RegisterTsdbPluginServer(s, &GRPCServer{p.Plugin})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *TsdbPluginImpl) GRPCClient(c *grpc.ClientConn) (interface{}, error) {
|
||||
return &GRPCClient{proto.NewTsdbPluginClient(c)}, nil
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package plugins
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"os/exec"
|
||||
"path"
|
||||
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/plugins/backend"
|
||||
"github.com/grafana/grafana/pkg/tsdb"
|
||||
|
||||
"github.com/grafana/grafana/pkg/log"
|
||||
proto "github.com/grafana/grafana/pkg/tsdb/models"
|
||||
shared "github.com/grafana/grafana/pkg/tsdb/models/proxy"
|
||||
plugin "github.com/hashicorp/go-plugin"
|
||||
)
|
||||
|
||||
type BackendDatasource struct {
|
||||
*PluginBase
|
||||
|
||||
Executable string
|
||||
client *plugin.Client
|
||||
}
|
||||
|
||||
type Killable interface {
|
||||
Kill()
|
||||
}
|
||||
|
||||
type NoopKiller struct{}
|
||||
|
||||
func (nk NoopKiller) Kill() {}
|
||||
|
||||
func (p *BackendDatasource) initBackendPlugin() (Killable, error) {
|
||||
logger := log.New("grafana.plugins")
|
||||
|
||||
p.client = plugin.NewClient(&plugin.ClientConfig{
|
||||
HandshakeConfig: plugin.HandshakeConfig{
|
||||
ProtocolVersion: 1,
|
||||
MagicCookieKey: "BASIC_PLUGIN",
|
||||
MagicCookieValue: "hello",
|
||||
},
|
||||
Plugins: map[string]plugin.Plugin{p.Id: &shared.TsdbPluginImpl{}},
|
||||
Cmd: exec.Command("sh", "-c", path.Join(p.PluginDir, p.Executable)),
|
||||
AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC},
|
||||
Logger: backend.LogWrapper{Logger: logger},
|
||||
})
|
||||
|
||||
rpcClient, err := p.client.Client()
|
||||
if err != nil {
|
||||
return NoopKiller{}, err
|
||||
}
|
||||
|
||||
raw, err := rpcClient.Dispense(p.Id)
|
||||
if err != nil {
|
||||
return NoopKiller{}, err
|
||||
}
|
||||
|
||||
plugin := raw.(shared.TsdbPlugin)
|
||||
response, err := plugin.Query(context.Background(), &proto.TsdbQuery{})
|
||||
|
||||
if err != nil {
|
||||
logger.Error("Response from plugin. ", "response", response)
|
||||
} else {
|
||||
logger.Info("Response from plugin. ", "response", response)
|
||||
}
|
||||
|
||||
tsdb.RegisterTsdbQueryEndpoint(p.Id, func(dsInfo *models.DataSource) (tsdb.TsdbQueryEndpoint, error) {
|
||||
return &shared.TsdbWrapper{TsdbPlugin: plugin}, nil
|
||||
})
|
||||
|
||||
return p.client, nil
|
||||
}
|
||||
|
||||
func (p *BackendDatasource) Kill() {
|
||||
p.client.Kill()
|
||||
}
|
||||
|
||||
func (p *BackendDatasource) Load(decoder *json.Decoder, pluginDir string) error {
|
||||
if err := decoder.Decode(&p); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := p.registerPlugin(pluginDir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
BackendDatasources[p.Id] = p
|
||||
return nil
|
||||
}
|
||||
+32
-11
@@ -17,12 +17,13 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
DataSources map[string]*DataSourcePlugin
|
||||
Panels map[string]*PanelPlugin
|
||||
StaticRoutes []*PluginStaticRoute
|
||||
Apps map[string]*AppPlugin
|
||||
Plugins map[string]*PluginBase
|
||||
PluginTypes map[string]interface{}
|
||||
DataSources map[string]*DataSourcePlugin
|
||||
Panels map[string]*PanelPlugin
|
||||
StaticRoutes []*PluginStaticRoute
|
||||
Apps map[string]*AppPlugin
|
||||
Plugins map[string]*PluginBase
|
||||
BackendDatasources map[string]*BackendDatasource
|
||||
PluginTypes map[string]interface{}
|
||||
|
||||
GrafanaLatestVersion string
|
||||
GrafanaHasUpdate bool
|
||||
@@ -34,7 +35,9 @@ type PluginScanner struct {
|
||||
errors []error
|
||||
}
|
||||
|
||||
func Init() error {
|
||||
type Dispose func()
|
||||
|
||||
func Init() (Dispose, error) {
|
||||
plog = log.New("plugins")
|
||||
|
||||
DataSources = make(map[string]*DataSourcePlugin)
|
||||
@@ -42,10 +45,12 @@ func Init() error {
|
||||
Panels = make(map[string]*PanelPlugin)
|
||||
Apps = make(map[string]*AppPlugin)
|
||||
Plugins = make(map[string]*PluginBase)
|
||||
BackendDatasources = make(map[string]*BackendDatasource)
|
||||
PluginTypes = map[string]interface{}{
|
||||
"panel": PanelPlugin{},
|
||||
"datasource": DataSourcePlugin{},
|
||||
"app": AppPlugin{},
|
||||
"panel": PanelPlugin{},
|
||||
"datasource": DataSourcePlugin{},
|
||||
"app": AppPlugin{},
|
||||
"backend-datasource": BackendDatasource{},
|
||||
}
|
||||
|
||||
plog.Info("Starting plugin search")
|
||||
@@ -77,10 +82,26 @@ func Init() error {
|
||||
app.initApp()
|
||||
}
|
||||
|
||||
killers := []Killable{}
|
||||
for _, be := range BackendDatasources {
|
||||
killable, err := be.initBackendPlugin()
|
||||
if err != nil {
|
||||
plog.Error("failed to init plugin", "id", be.Id, "error", err)
|
||||
} else {
|
||||
killers = append(killers, killable)
|
||||
}
|
||||
}
|
||||
|
||||
go StartPluginUpdateChecker()
|
||||
go updateAppDashboards()
|
||||
|
||||
return nil
|
||||
return dispose, nil
|
||||
}
|
||||
|
||||
func dispose() {
|
||||
for _, p := range BackendDatasources {
|
||||
p.Kill()
|
||||
}
|
||||
}
|
||||
|
||||
func checkPluginPaths() error {
|
||||
|
||||
Reference in New Issue
Block a user