MM-8622: Improved plugin error reporting (#8737)
* allow `Wait()`ing on the supervisor In the event the plugin supervisor shuts down a plugin for crashing too many times, the new `Wait()` interface allows the `ActivatePlugin` to accept a callback function to trigger when `supervisor.Wait()` returns. If the supervisor shuts down normally, this callback is invoked with a nil error, otherwise any error reported by the supervisor is passed along. * improve plugin activation/deactivation logic Avoid triggering activation of previously failed-to-start plugins just becase something in the configuration changed. Now, intelligently compare the global enable bit as well as the each individual plugin's enabled bit. * expose store to manipulate PluginStatuses * expose API to fetch plugin statuses * keep track of whether or not plugin sandboxing is supported * transition plugin statuses * restore error on plugin activation if already active * don't initialize test plugins until successfully loaded * emit websocket events when plugin statuses change * skip pruning if already initialized * MM-8622: maintain plugin statuses in memory Switch away from persisting plugin statuses to the database, and maintain in memory instead. This will be followed by a cluster interface to query the in-memory status of plugin statuses from all cluster nodes. At the same time, rename `cluster_discovery_id` on the `PluginStatus` model object to `cluster_id`. * MM-8622: aggregate plugin statuses across cluster * fetch cluster plugin statuses when emitting websocket notification * address unit test fixes after rebasing * relax (poor) racey unit test re: supervisor.Wait() * make store-mocks
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
5c21bdc178
Коммит
847c181ec9
@@ -108,7 +108,7 @@ func (env *Environment) IsPluginActive(pluginId string) bool {
|
||||
}
|
||||
|
||||
// Activates the plugin with the given id.
|
||||
func (env *Environment) ActivatePlugin(id string) error {
|
||||
func (env *Environment) ActivatePlugin(id string, onError func(error)) error {
|
||||
env.mutex.Lock()
|
||||
defer env.mutex.Unlock()
|
||||
|
||||
@@ -117,7 +117,7 @@ func (env *Environment) ActivatePlugin(id string) error {
|
||||
}
|
||||
|
||||
if _, ok := env.activePlugins[id]; ok {
|
||||
return nil
|
||||
return fmt.Errorf("plugin already active: %v", id)
|
||||
}
|
||||
plugins, err := ScanSearchPath(env.searchPath)
|
||||
if err != nil {
|
||||
@@ -156,6 +156,14 @@ func (env *Environment) ActivatePlugin(id string) error {
|
||||
if err := supervisor.Start(api); err != nil {
|
||||
return errors.Wrapf(err, "unable to start plugin: %v", id)
|
||||
}
|
||||
if onError != nil {
|
||||
go func() {
|
||||
err := supervisor.Wait()
|
||||
if err != nil {
|
||||
onError(err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
activePlugin.Supervisor = supervisor
|
||||
}
|
||||
|
||||
@@ -56,6 +56,10 @@ func (m *MockSupervisor) Hooks() plugin.Hooks {
|
||||
return m.Called().Get(0).(plugin.Hooks)
|
||||
}
|
||||
|
||||
func (m *MockSupervisor) Wait() error {
|
||||
return m.Called().Get(0).(error)
|
||||
}
|
||||
|
||||
func initTmpDir(t *testing.T, files map[string]string) string {
|
||||
success := false
|
||||
dir, err := ioutil.TempDir("", "mm-plugin-test")
|
||||
@@ -130,7 +134,7 @@ func TestEnvironment(t *testing.T) {
|
||||
activePlugins := env.ActivePlugins()
|
||||
assert.Len(t, activePlugins, 0)
|
||||
|
||||
assert.Error(t, env.ActivatePlugin("x"))
|
||||
assert.Error(t, env.ActivatePlugin("x", nil))
|
||||
|
||||
var api struct{ plugin.API }
|
||||
var supervisor MockSupervisor
|
||||
@@ -145,11 +149,11 @@ func TestEnvironment(t *testing.T) {
|
||||
supervisor.On("Stop").Return(nil)
|
||||
supervisor.On("Hooks").Return(&hooks)
|
||||
|
||||
assert.NoError(t, env.ActivatePlugin("foo"))
|
||||
assert.NoError(t, env.ActivatePlugin("foo", nil))
|
||||
assert.Equal(t, env.ActivePluginIds(), []string{"foo"})
|
||||
activePlugins = env.ActivePlugins()
|
||||
assert.Len(t, activePlugins, 1)
|
||||
assert.NoError(t, env.ActivatePlugin("foo"))
|
||||
assert.Error(t, env.ActivatePlugin("foo", nil))
|
||||
assert.True(t, env.IsPluginActive("foo"))
|
||||
|
||||
hooks.On("OnDeactivate").Return(nil)
|
||||
@@ -157,7 +161,7 @@ func TestEnvironment(t *testing.T) {
|
||||
assert.Error(t, env.DeactivatePlugin("foo"))
|
||||
assert.False(t, env.IsPluginActive("foo"))
|
||||
|
||||
assert.NoError(t, env.ActivatePlugin("foo"))
|
||||
assert.NoError(t, env.ActivatePlugin("foo", nil))
|
||||
assert.Equal(t, env.ActivePluginIds(), []string{"foo"})
|
||||
|
||||
assert.Equal(t, env.SearchPath(), dir)
|
||||
@@ -184,7 +188,7 @@ func TestEnvironment_DuplicatePluginError(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
defer env.Shutdown()
|
||||
|
||||
assert.Error(t, env.ActivatePlugin("foo"))
|
||||
assert.Error(t, env.ActivatePlugin("foo", nil))
|
||||
assert.Empty(t, env.ActivePluginIds())
|
||||
}
|
||||
|
||||
@@ -200,7 +204,7 @@ func TestEnvironment_BadSearchPathError(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
defer env.Shutdown()
|
||||
|
||||
assert.Error(t, env.ActivatePlugin("foo"))
|
||||
assert.Error(t, env.ActivatePlugin("foo", nil))
|
||||
assert.Empty(t, env.ActivePluginIds())
|
||||
}
|
||||
|
||||
@@ -244,7 +248,7 @@ func TestEnvironment_ActivatePluginErrors(t *testing.T) {
|
||||
hooks.Mock = mock.Mock{}
|
||||
provider.Mock = mock.Mock{}
|
||||
setup()
|
||||
assert.Error(t, env.ActivatePlugin("foo"))
|
||||
assert.Error(t, env.ActivatePlugin("foo", nil))
|
||||
assert.Empty(t, env.ActivePluginIds())
|
||||
supervisor.AssertExpectations(t)
|
||||
hooks.AssertExpectations(t)
|
||||
@@ -285,7 +289,7 @@ func TestEnvironment_ShutdownError(t *testing.T) {
|
||||
|
||||
hooks.On("OnDeactivate").Return(fmt.Errorf("test error"))
|
||||
|
||||
assert.NoError(t, env.ActivatePlugin("foo"))
|
||||
assert.NoError(t, env.ActivatePlugin("foo", nil))
|
||||
assert.Equal(t, env.ActivePluginIds(), []string{"foo"})
|
||||
assert.Len(t, env.Shutdown(), 2)
|
||||
}
|
||||
@@ -332,7 +336,7 @@ func TestEnvironment_ConcurrentHookInvocations(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
assert.NoError(t, env.ActivatePlugin("foo"))
|
||||
assert.NoError(t, env.ActivatePlugin("foo", nil))
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
@@ -391,7 +395,7 @@ func TestEnvironment_HooksForPlugins(t *testing.T) {
|
||||
Text: "bar",
|
||||
}, nil)
|
||||
|
||||
assert.NoError(t, env.ActivatePlugin("foo"))
|
||||
assert.NoError(t, env.ActivatePlugin("foo", nil))
|
||||
assert.Equal(t, env.ActivePluginIds(), []string{"foo"})
|
||||
|
||||
resp, appErr, err := env.HooksForPlugin("foo").ExecuteCommand(&model.CommandArgs{
|
||||
|
||||
Ссылка в новой задаче
Block a user