quorum/plugin/base.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
}