github.com/haalcala/mattermost-server-change-repo@v0.0.0-20210713015153-16753fbeee5f/plugin/supervisor.go (about) 1 // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2 // See LICENSE.txt for license information. 3 4 package plugin 5 6 import ( 7 "fmt" 8 "os/exec" 9 "path/filepath" 10 "runtime" 11 "strings" 12 "sync" 13 "time" 14 15 plugin "github.com/hashicorp/go-plugin" 16 17 "github.com/mattermost/mattermost-server/v5/einterfaces" 18 "github.com/mattermost/mattermost-server/v5/mlog" 19 "github.com/mattermost/mattermost-server/v5/model" 20 ) 21 22 type supervisor struct { 23 lock sync.RWMutex 24 client *plugin.Client 25 hooks Hooks 26 implemented [TotalHooksId]bool 27 pid int 28 } 29 30 func newSupervisor(pluginInfo *model.BundleInfo, apiImpl API, parentLogger *mlog.Logger, metrics einterfaces.MetricsInterface) (retSupervisor *supervisor, retErr error) { 31 sup := supervisor{} 32 defer func() { 33 if retErr != nil { 34 sup.Shutdown() 35 } 36 }() 37 38 wrappedLogger := pluginInfo.WrapLogger(parentLogger) 39 40 hclogAdaptedLogger := &hclogAdapter{ 41 wrappedLogger: wrappedLogger.WithCallerSkip(1), 42 extrasKey: "wrapped_extras", 43 } 44 45 pluginMap := map[string]plugin.Plugin{ 46 "hooks": &hooksPlugin{ 47 log: wrappedLogger, 48 apiImpl: &apiTimerLayer{pluginInfo.Manifest.Id, apiImpl, metrics}, 49 }, 50 } 51 52 executable := filepath.Clean(filepath.Join( 53 ".", 54 pluginInfo.Manifest.GetExecutableForRuntime(runtime.GOOS, runtime.GOARCH), 55 )) 56 if strings.HasPrefix(executable, "..") { 57 return nil, fmt.Errorf("invalid backend executable") 58 } 59 executable = filepath.Join(pluginInfo.Path, executable) 60 61 cmd := exec.Command(executable) 62 63 sup.client = plugin.NewClient(&plugin.ClientConfig{ 64 HandshakeConfig: handshake, 65 Plugins: pluginMap, 66 Cmd: cmd, 67 SyncStdout: wrappedLogger.With(mlog.String("source", "plugin_stdout")).StdLogWriter(), 68 SyncStderr: wrappedLogger.With(mlog.String("source", "plugin_stderr")).StdLogWriter(), 69 Logger: hclogAdaptedLogger, 70 StartTimeout: time.Second * 3, 71 }) 72 73 rpcClient, err := sup.client.Client() 74 if err != nil { 75 return nil, err 76 } 77 78 sup.pid = cmd.Process.Pid 79 80 raw, err := rpcClient.Dispense("hooks") 81 if err != nil { 82 return nil, err 83 } 84 85 sup.hooks = &hooksTimerLayer{pluginInfo.Manifest.Id, raw.(Hooks), metrics} 86 87 impl, err := sup.hooks.Implemented() 88 if err != nil { 89 return nil, err 90 } 91 for _, hookName := range impl { 92 if hookId, ok := hookNameToId[hookName]; ok { 93 sup.implemented[hookId] = true 94 } 95 } 96 97 return &sup, nil 98 } 99 100 func (sup *supervisor) Shutdown() { 101 sup.lock.RLock() 102 defer sup.lock.RUnlock() 103 if sup.client != nil { 104 sup.client.Kill() 105 } 106 } 107 108 func (sup *supervisor) Hooks() Hooks { 109 sup.lock.RLock() 110 defer sup.lock.RUnlock() 111 return sup.hooks 112 } 113 114 // PerformHealthCheck checks the plugin through an an RPC ping. 115 func (sup *supervisor) PerformHealthCheck() error { 116 // No need for a lock here because Ping is read-locked. 117 if pingErr := sup.Ping(); pingErr != nil { 118 for pingFails := 1; pingFails < HealthCheckPingFailLimit; pingFails++ { 119 pingErr = sup.Ping() 120 if pingErr == nil { 121 break 122 } 123 } 124 if pingErr != nil { 125 mlog.Debug("Error pinging plugin", mlog.Err(pingErr)) 126 return fmt.Errorf("Plugin RPC connection is not responding") 127 } 128 } 129 130 return nil 131 } 132 133 // Ping checks that the RPC connection with the plugin is alive and healthy. 134 func (sup *supervisor) Ping() error { 135 sup.lock.RLock() 136 defer sup.lock.RUnlock() 137 client, err := sup.client.Client() 138 if err != nil { 139 return err 140 } 141 142 return client.Ping() 143 } 144 145 func (sup *supervisor) Implements(hookId int) bool { 146 sup.lock.RLock() 147 defer sup.lock.RUnlock() 148 return sup.implemented[hookId] 149 }