mirror of https://github.com/poanetwork/quorum.git
276 lines
6.7 KiB
Go
276 lines
6.7 KiB
Go
package plugin
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
slog "log"
|
|
"os"
|
|
"os/exec"
|
|
"path"
|
|
"path/filepath"
|
|
"reflect"
|
|
"time"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
iplugin "github.com/ethereum/go-ethereum/internal/plugin"
|
|
"github.com/ethereum/go-ethereum/log"
|
|
"github.com/ethereum/go-ethereum/plugin/initializer"
|
|
"github.com/hashicorp/go-hclog"
|
|
"github.com/hashicorp/go-plugin"
|
|
)
|
|
|
|
type managedPlugin interface {
|
|
Start() error
|
|
Stop() error
|
|
|
|
Info() (PluginInterfaceName, interface{})
|
|
}
|
|
|
|
// Plugin-meta.json
|
|
type MetaData struct {
|
|
Version string `json:"version"`
|
|
Os string `json:"os"`
|
|
Arch string `json:"arch"`
|
|
EntryPoint string `json:"entrypoint"`
|
|
Parameters []string `json:"parameters,omitempty"`
|
|
}
|
|
|
|
type basePlugin struct {
|
|
pm *PluginManager
|
|
pluginInterface PluginInterfaceName // plugin provider name
|
|
pluginDefinition *PluginDefinition
|
|
client *plugin.Client
|
|
gateways plugin.PluginSet // gateways to invoke RPC API implementation of interfaces supported by this plugin
|
|
pluginWorkspace string // plugin workspace
|
|
commands []string // plugin executable commands
|
|
logger log.Logger
|
|
}
|
|
|
|
var basePluginPointerType = reflect.TypeOf(&basePlugin{})
|
|
|
|
func newBasePlugin(pm *PluginManager, pluginInterface PluginInterfaceName, pluginDefinition PluginDefinition, gateways plugin.PluginSet) (*basePlugin, error) {
|
|
gateways[initializer.ConnectorName] = &initializer.PluginConnector{}
|
|
|
|
// build basePlugin
|
|
return &basePlugin{
|
|
pm: pm,
|
|
pluginInterface: pluginInterface,
|
|
logger: log.New("provider", pluginInterface, "plugin", pluginDefinition.Name, "version", pluginDefinition.Version),
|
|
pluginDefinition: &pluginDefinition,
|
|
gateways: gateways,
|
|
}, nil
|
|
|
|
}
|
|
|
|
// metadata.Command must be populated correctly here
|
|
func (bp *basePlugin) load() error {
|
|
// Get plugin distribution path
|
|
pluginDistFilePath, err := bp.pm.downloader.Download(bp.pluginDefinition)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// get file checksum
|
|
pluginChecksum, err := bp.checksum(pluginDistFilePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
bp.logger.Info("verifying plugin integrity", "checksum", pluginChecksum)
|
|
if err := bp.pm.verifier.VerifySignature(bp.pluginDefinition, pluginChecksum); err != nil {
|
|
return err
|
|
}
|
|
bp.logger.Info("unpacking plugin", "checksum", pluginChecksum)
|
|
// Unpack plugin
|
|
unPackDir, pluginMeta, err := unpackPlugin(pluginDistFilePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// Create Execution Command
|
|
var command *exec.Cmd
|
|
executable := path.Join(unPackDir, pluginMeta.EntryPoint)
|
|
if !common.FileExist(executable) {
|
|
return fmt.Errorf("entry point does not exist")
|
|
}
|
|
bp.logger.Debug("Plugin executable", "path", executable)
|
|
if len(pluginMeta.Parameters) == 0 {
|
|
command = exec.Command(executable)
|
|
bp.commands = []string{executable}
|
|
} else {
|
|
command = exec.Command(executable, pluginMeta.Parameters...)
|
|
bp.commands = append([]string{executable}, pluginMeta.Parameters...)
|
|
}
|
|
command.Dir = unPackDir
|
|
bp.client = plugin.NewClient(&plugin.ClientConfig{
|
|
HandshakeConfig: iplugin.DefaultHandshakeConfig,
|
|
Plugins: bp.gateways,
|
|
AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC},
|
|
Cmd: command,
|
|
AutoMTLS: true,
|
|
Logger: &logDelegate{bp.logger.New("from", "plugin")},
|
|
})
|
|
|
|
bp.pluginWorkspace = unPackDir
|
|
return nil
|
|
}
|
|
|
|
func (bp *basePlugin) Start() (err error) {
|
|
startTime := time.Now()
|
|
defer func(startTime time.Time) {
|
|
if err == nil {
|
|
bp.logger.Info("Plugin started", "took", time.Since(startTime))
|
|
} else {
|
|
bp.logger.Error("Plugin failed to start", "error", err, "took", time.Since(startTime))
|
|
_ = bp.Stop()
|
|
}
|
|
}(startTime)
|
|
bp.logger.Info("Starting plugin")
|
|
err = bp.load()
|
|
if err != nil {
|
|
return
|
|
}
|
|
_, err = bp.client.Client()
|
|
if err != nil {
|
|
return
|
|
}
|
|
err = bp.init()
|
|
return
|
|
}
|
|
|
|
func (bp *basePlugin) Stop() error {
|
|
if bp.client != nil {
|
|
bp.client.Kill()
|
|
}
|
|
if bp.pluginWorkspace == "" {
|
|
return nil
|
|
}
|
|
return bp.cleanPluginWorkspace()
|
|
}
|
|
|
|
func (bp *basePlugin) cleanPluginWorkspace() error {
|
|
workspace, err := os.Open(bp.pluginWorkspace)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer workspace.Close()
|
|
names, err := workspace.Readdirnames(-1)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, name := range names {
|
|
err = os.RemoveAll(filepath.Join(bp.pluginWorkspace, name))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (bp *basePlugin) init() error {
|
|
bp.logger.Info("Initializing plugin")
|
|
raw, err := bp.dispense(initializer.ConnectorName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c, ok := raw.(initializer.PluginInitializer)
|
|
if !ok {
|
|
return fmt.Errorf("missing plugin initializer. Make sure it is in the plugin set")
|
|
}
|
|
rawConfig, err := bp.pluginDefinition.ReadConfig()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return c.Init(context.Background(), bp.pm.nodeName, rawConfig)
|
|
}
|
|
|
|
func (bp *basePlugin) dispense(name string) (interface{}, error) {
|
|
rpcClient, err := bp.client.Client()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return rpcClient.Dispense(name)
|
|
}
|
|
|
|
func (bp *basePlugin) Config() *PluginDefinition {
|
|
return bp.pluginDefinition
|
|
}
|
|
|
|
func (bp *basePlugin) checksum(pluginFile string) (string, error) {
|
|
return getSha256Checksum(pluginFile)
|
|
}
|
|
|
|
func (bp *basePlugin) Info() (PluginInterfaceName, interface{}) {
|
|
info := make(map[string]interface{})
|
|
info["name"] = bp.pluginDefinition.Name
|
|
info["version"] = bp.pluginDefinition.Version
|
|
info["config"] = bp.pluginDefinition.Config
|
|
info["executable"] = bp.commands
|
|
return bp.pluginInterface, info
|
|
}
|
|
|
|
type logDelegate struct {
|
|
eLogger log.Logger
|
|
}
|
|
|
|
func (ld *logDelegate) Trace(msg string, args ...interface{}) {
|
|
ld.eLogger.Trace(msg, args...)
|
|
}
|
|
|
|
func (ld *logDelegate) Debug(msg string, args ...interface{}) {
|
|
ld.eLogger.Debug(msg, args...)
|
|
}
|
|
|
|
func (ld *logDelegate) Info(msg string, args ...interface{}) {
|
|
ld.eLogger.Info(msg, args...)
|
|
}
|
|
|
|
func (ld *logDelegate) Warn(msg string, args ...interface{}) {
|
|
ld.eLogger.Warn(msg, args...)
|
|
}
|
|
|
|
func (ld *logDelegate) Error(msg string, args ...interface{}) {
|
|
ld.eLogger.Error(msg, args...)
|
|
}
|
|
|
|
func (ld *logDelegate) IsTrace() bool {
|
|
return true
|
|
}
|
|
|
|
func (*logDelegate) IsDebug() bool {
|
|
return true
|
|
}
|
|
|
|
func (*logDelegate) IsInfo() bool {
|
|
return true
|
|
}
|
|
|
|
func (*logDelegate) IsWarn() bool {
|
|
return true
|
|
}
|
|
|
|
func (*logDelegate) IsError() bool {
|
|
return true
|
|
}
|
|
|
|
func (ld *logDelegate) With(args ...interface{}) hclog.Logger {
|
|
return &logDelegate{ld.eLogger.New(args...)}
|
|
}
|
|
|
|
func (ld *logDelegate) Named(name string) hclog.Logger {
|
|
return ld
|
|
}
|
|
|
|
func (ld *logDelegate) ResetNamed(name string) hclog.Logger {
|
|
return ld
|
|
}
|
|
|
|
func (ld *logDelegate) SetLevel(level hclog.Level) {
|
|
}
|
|
|
|
func (*logDelegate) StandardLogger(opts *hclog.StandardLoggerOptions) *slog.Logger {
|
|
return nil
|
|
}
|
|
|
|
func (*logDelegate) StandardWriter(opts *hclog.StandardLoggerOptions) io.Writer {
|
|
return nil
|
|
}
|