Files
mc-gateway/cmd/gateway/plugin.go
tursom 91eee02243
Some checks failed
Go / build (.exe, 386, windows, windows-386) (push) Has been cancelled
Go / build (.exe, amd64, windows, windows-amd64) (push) Has been cancelled
Go / build (.exe, arm64, windows, windows-arm64) (push) Has been cancelled
Go / build (386, freebsd, freebsd-386) (push) Has been cancelled
Go / build (386, linux, linux-386) (push) Has been cancelled
Go / build (386, netbsd, netbsd-386) (push) Has been cancelled
Go / build (386, openbsd, openbsd-386) (push) Has been cancelled
Go / build (386, plan9, plan9-386) (push) Has been cancelled
Go / build (amd64, darwin, darwin-amd64) (push) Has been cancelled
Go / build (amd64, dragonfly, dragonfly-amd64) (push) Has been cancelled
Go / build (amd64, freebsd, freebsd-amd64) (push) Has been cancelled
Go / build (amd64, illumos, illumos-amd64) (push) Has been cancelled
Go / build (amd64, linux, linux-amd64) (push) Has been cancelled
Go / build (amd64, netbsd, netbsd-amd64) (push) Has been cancelled
Go / build (amd64, openbsd, openbsd-amd64) (push) Has been cancelled
Go / build (amd64, plan9, plan9-amd64) (push) Has been cancelled
Go / build (amd64, solaris, solaris-amd64) (push) Has been cancelled
Go / build (arm, 6, linux, linux-armv6) (push) Has been cancelled
Go / build (arm, 7, linux, linux-armv7) (push) Has been cancelled
Go / build (arm, freebsd, freebsd-arm) (push) Has been cancelled
Go / build (arm, netbsd, netbsd-arm) (push) Has been cancelled
Go / build (arm, openbsd, openbsd-arm) (push) Has been cancelled
Go / build (arm, plan9, plan9-arm) (push) Has been cancelled
Go / build (arm64, darwin, darwin-arm64) (push) Has been cancelled
Go / build (arm64, freebsd, freebsd-arm64) (push) Has been cancelled
Go / build (arm64, linux, linux-arm64) (push) Has been cancelled
Go / build (arm64, netbsd, netbsd-arm64) (push) Has been cancelled
Go / build (arm64, openbsd, openbsd-arm64) (push) Has been cancelled
Go / build (loong64, linux, linux-loong64) (push) Has been cancelled
Go / build (mips, linux, linux-mips) (push) Has been cancelled
Go / build (mips64, linux, linux-mips64) (push) Has been cancelled
Go / build (mips64le, linux, linux-mips64le) (push) Has been cancelled
Go / build (mipsle, linux, linux-mipsle) (push) Has been cancelled
Go / build (ppc64, aix, aix-ppc64) (push) Has been cancelled
Go / build (ppc64, linux, linux-ppc64) (push) Has been cancelled
Go / build (ppc64, openbsd, openbsd-ppc64) (push) Has been cancelled
Go / build (ppc64le, linux, linux-ppc64le) (push) Has been cancelled
Go / build (riscv64, freebsd, freebsd-riscv64) (push) Has been cancelled
Go / build (riscv64, linux, linux-riscv64) (push) Has been cancelled
Go / build (riscv64, openbsd, openbsd-riscv64) (push) Has been cancelled
Go / build (s390x, linux, linux-s390x) (push) Has been cancelled
Go / merge-artifacts (push) Has been cancelled
Docker Image / docker (push) Has been cancelled
feat(plugin): add observability operations
2026-06-26 12:32:45 +08:00

254 lines
6.5 KiB
Go

package main
import (
"context"
"errors"
"net"
"plugin"
"sync"
"time"
"github.com/rs/zerolog/log"
"github.com/tursom/mc-gateway/plugin/api"
)
var (
exitWaitGroup sync.WaitGroup
pluginLock sync.RWMutex
plugins = make(map[string]api.Plugin)
hooks = make(map[string]map[string]any)
)
type (
Gateway struct {
pluginId string
}
)
func loadPlugins() {
pluginLock.Lock()
defer pluginLock.Unlock()
for pluginKey, pluginConfig := range config.Plugin {
if enable, ok := pluginConfig["enable"].(bool); ok && enable {
pluginFile := pluginKey
if file, ok := pluginConfig["file"].(string); ok {
pluginFile = file
}
if _, ok := plugins[pluginKey]; ok {
continue
}
gateway := &Gateway{
pluginId: pluginKey,
}
log.Info().Str("plugin", pluginKey).Msg("Loading plugin")
p, err := plugin.Open(pluginFile + ".so")
if err != nil {
log.Err(err).Str("plugin", pluginKey).Msg("Failed to open plugin")
continue
}
pluginSymbol, err := p.Lookup("Plugin")
if err != nil {
log.Err(err).Str("plugin", pluginKey).Msg("Failed to lookup plugin")
continue
}
pluginFactory, ok := pluginSymbol.(func() api.Plugin)
if !ok {
log.Err(err).Str("plugin", pluginKey).Msg("Invalid plugin factory signature")
continue
}
pluginInstance := pluginFactory()
cfgObj := pluginInstance.NewConfigObj()
if err := loadPluginConfig(pluginConfig, cfgObj); err != nil {
log.Err(err).Str("plugin", pluginKey).Msg("Failed to load plugin config")
continue
}
if err := pluginInstance.ReloadConfig(cfgObj); err != nil {
log.Err(err).Str("plugin", pluginKey).Msg("Failed to reload plugin config")
continue
}
if err := pluginInstance.Init(gateway); err != nil {
log.Err(err).Str("plugin", pluginKey).Msg("Failed to initialize plugin")
continue
}
plugins[pluginKey] = pluginInstance
hooks[pluginKey] = make(map[string]any)
log.Info().Str("plugin", pluginKey).Msg("Plugin loaded successfully")
} else {
if plugin, ok := plugins[pluginKey]; ok {
if err := plugin.Destroy(); err != nil {
log.Err(err).Str("plugin", pluginKey).Msg("Failed to destroy plugin")
}
log.Info().Str("plugin", pluginKey).Msg("Plugin disabled")
}
delete(plugins, pluginKey)
delete(hooks, pluginKey)
}
}
}
// HandleConn implements api.Gateway.
func (g *Gateway) HandleConn(conn net.Conn) {
go handleRequest(conn)
}
// Hook implements api.Gateway.
func (g *Gateway) Hook(hook string, handler any) error {
pluginLock.Lock()
defer pluginLock.Unlock()
hooks[g.pluginId][hook] = handler
return nil
}
// ExitWaitGroup implements api.Gateway.
func (g *Gateway) ExitWaitGroup() *sync.WaitGroup {
return &exitWaitGroup
}
func (g *Gateway) EmitEvent(ctx context.Context, name string, fields map[string]string) error {
_ = ctx
_ = name
_ = fields
return nil
}
func (g *Gateway) ObserveMetric(ctx context.Context, name string, value float64, labels map[string]string) error {
_ = ctx
_ = name
_ = value
_ = labels
return nil
}
func (g *Gateway) Logger() api.Logger {
return noopPluginLogger{}
}
func (g *Gateway) DataStore() api.DataStore {
return noopPluginDataStore{}
}
func (g *Gateway) FileStore() api.FileStore {
return noopPluginFileStore{}
}
func (g *Gateway) ExternalClient(name string) api.ExternalClient {
_ = name
return noopExternalClient{}
}
func (g *Gateway) RegisterBackgroundTask(task api.BackgroundTask) error {
_ = task
return nil
}
// TestOp implements api.Gateway.
func (g *Gateway) TestOp() {
panic("unimplemented")
}
type noopPluginLogger struct{}
func (noopPluginLogger) Debug(context.Context, string, map[string]string) {}
func (noopPluginLogger) Info(context.Context, string, map[string]string) {}
func (noopPluginLogger) Warn(context.Context, string, map[string]string) {}
func (noopPluginLogger) Error(context.Context, string, map[string]string) {}
type noopPluginDataStore struct{}
func (noopPluginDataStore) Put(context.Context, api.DataRecord) error { return nil }
func (noopPluginDataStore) Get(context.Context, string) (api.DataRecord, error) {
return api.DataRecord{}, errors.New("plugin data store is unavailable")
}
func (noopPluginDataStore) Delete(context.Context, string) error { return nil }
type noopPluginFileStore struct{}
func (noopPluginFileStore) ResourcePath(string) (string, error) {
return "", errors.New("plugin file store is unavailable")
}
func (noopPluginFileStore) Write(context.Context, string, string, []byte, string, time.Duration) error {
return nil
}
func (noopPluginFileStore) Read(context.Context, string, string, int64) ([]byte, error) {
return nil, errors.New("plugin file store is unavailable")
}
func (noopPluginFileStore) Delete(context.Context, string, string) error { return nil }
type noopExternalClient struct{}
func (noopExternalClient) DoHTTP(context.Context, api.ExternalRequest) (api.ExternalResponse, error) {
return api.ExternalResponse{}, errors.New("external client is unavailable")
}
func (noopExternalClient) DialTCP(context.Context, string, time.Duration) (net.Conn, error) {
return nil, errors.New("external client is unavailable")
}
func (noopExternalClient) HealthCheck(context.Context) error { return nil }
func Handler1[T1, R any](t1 T1) func(func(T1) R) R {
return func(acceptor func(T1) R) R {
return acceptor(t1)
}
}
func Handler2[T1, T2, R any](t1 T1, t2 T2) func(func(T1, T2) R) R {
return func(acceptor func(T1, T2) R) R {
return acceptor(t1, t2)
}
}
func Handler1R2[T1, R1, R2 any](t1 T1) func(func(T1) (R1, R2)) (R1, R2) {
return func(acceptor func(T1) (R1, R2)) (R1, R2) {
return acceptor(t1)
}
}
func invokeFirstHookHandler[Acceptor, Handler any](
hook api.HookType[Acceptor, Handler],
acceptor func(Acceptor) bool,
handelr func(Handler) error,
) (bool, error) {
pluginLock.RLock()
defer pluginLock.RUnlock()
for _, handlers := range hooks {
if handler, ok := handlers[hook.Key()].(api.HookHandler[Acceptor, Handler]); ok && acceptor(handler.Acceptor()) {
if err := handelr(handler.Handler()); err != nil {
return true, err
}
return true, nil
}
}
return false, nil
}
func invokeAllHookHandler[Acceptor, Handler any](
hook api.HookType[Acceptor, Handler],
acceptor func(Acceptor) bool,
handelr func(Handler) error,
) error {
pluginLock.RLock()
defer pluginLock.RUnlock()
for _, handlers := range hooks {
if handler, ok := handlers[hook.Key()].(api.HookHandler[Acceptor, Handler]); ok && acceptor(handler.Acceptor()) {
if err := handelr(handler.Handler()); err != nil {
return err
}
}
}
return nil
}