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  }