Chore: Soft failure in plugin initialization (#66210)

This commit is contained in:
Andres Martinez Gotor 2023-04-12 11:34:16 +02:00 committed by GitHub
parent 48de17945f
commit 52f39e6fa0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 125 additions and 12 deletions

View File

@ -299,23 +299,28 @@ func (m *FakeProcessManager) Stop(ctx context.Context, pluginID string) error {
}
type FakeBackendProcessProvider struct {
Requested map[string]int
Invoked map[string]int
Requested map[string]int
Invoked map[string]int
BackendFactoryFunc func(context.Context, *plugins.Plugin) backendplugin.PluginFactoryFunc
}
func NewFakeBackendProcessProvider() *FakeBackendProcessProvider {
return &FakeBackendProcessProvider{
f := &FakeBackendProcessProvider{
Requested: make(map[string]int),
Invoked: make(map[string]int),
}
f.BackendFactoryFunc = func(ctx context.Context, p *plugins.Plugin) backendplugin.PluginFactoryFunc {
f.Requested[p.ID]++
return func(pluginID string, _ log.Logger, _ []string) (backendplugin.Plugin, error) {
f.Invoked[pluginID]++
return &FakePluginClient{}, nil
}
}
return f
}
func (pr *FakeBackendProcessProvider) BackendFactory(_ context.Context, p *plugins.Plugin) backendplugin.PluginFactoryFunc {
pr.Requested[p.ID]++
return func(pluginID string, _ log.Logger, _ []string) (backendplugin.Plugin, error) {
pr.Invoked[pluginID]++
return &FakePluginClient{}, nil
}
func (pr *FakeBackendProcessProvider) BackendFactory(ctx context.Context, p *plugins.Plugin) backendplugin.PluginFactoryFunc {
return pr.BackendFactoryFunc(ctx, p)
}
type FakeLicensingService struct {

View File

@ -161,17 +161,22 @@ func (l *Loader) loadPlugins(ctx context.Context, src plugins.PluginSource, foun
verifiedPlugins = append(verifiedPlugins, plugin)
}
// initialize plugins
initializedPlugins := make([]*plugins.Plugin, 0)
for _, p := range verifiedPlugins {
err := l.pluginInitializer.Initialize(ctx, p)
if err != nil {
return nil, err
l.log.Error("Could not initialize plugin", "pluginId", p.ID, "err", err)
continue
}
if errDeclareRoles := l.roleRegistry.DeclarePluginRoles(ctx, p.ID, p.Name, p.Roles); errDeclareRoles != nil {
l.log.Warn("Declare plugin roles failed.", "pluginID", p.ID, "err", errDeclareRoles)
}
initializedPlugins = append(initializedPlugins, p)
}
for _, p := range verifiedPlugins {
for _, p := range initializedPlugins {
if err := l.load(ctx, p); err != nil {
l.log.Error("Could not start plugin", "pluginId", p.ID, "err", err)
}
@ -181,7 +186,7 @@ func (l *Loader) loadPlugins(ctx context.Context, src plugins.PluginSource, foun
}
}
return verifiedPlugins, nil
return initializedPlugins, nil
}
func (l *Loader) Unload(ctx context.Context, pluginID string) error {

View File

@ -2,6 +2,7 @@ package loader
import (
"context"
"fmt"
"os"
"path/filepath"
"sort"
@ -12,6 +13,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/backendplugin"
"github.com/grafana/grafana/pkg/plugins/config"
"github.com/grafana/grafana/pkg/plugins/log"
"github.com/grafana/grafana/pkg/plugins/manager/fakes"
@ -1014,6 +1016,107 @@ func TestLoader_Load_DuplicatePlugins(t *testing.T) {
})
}
func TestLoader_Load_SkipUninitializedPlugins(t *testing.T) {
t.Run("Load duplicate plugin folders", func(t *testing.T) {
pluginDir1, err := filepath.Abs("../testdata/test-app")
if err != nil {
t.Errorf("could not construct absolute path of plugin dir")
return
}
pluginDir2, err := filepath.Abs("../testdata/valid-v2-signature")
if err != nil {
t.Errorf("could not construct absolute path of plugin dir")
return
}
expected := []*plugins.Plugin{
{
JSONData: plugins.JSONData{
ID: "test-app",
Type: "app",
Name: "Test App",
Info: plugins.Info{
Author: plugins.InfoLink{
Name: "Test Inc.",
URL: "http://test.com",
},
Description: "Official Grafana Test App & Dashboard bundle",
Version: "1.0.0",
Links: []plugins.InfoLink{
{Name: "Project site", URL: "http://project.com"},
{Name: "License & Terms", URL: "http://license.com"},
},
Logos: plugins.Logos{
Small: "public/plugins/test-app/img/logo_small.png",
Large: "public/plugins/test-app/img/logo_large.png",
},
Screenshots: []plugins.Screenshots{
{Path: "public/plugins/test-app/img/screenshot1.png", Name: "img1"},
{Path: "public/plugins/test-app/img/screenshot2.png", Name: "img2"},
},
Updated: "2015-02-10",
},
Dependencies: plugins.Dependencies{
GrafanaVersion: "3.x.x",
Plugins: []plugins.Dependency{
{Type: "datasource", ID: "graphite", Name: "Graphite", Version: "1.0.0"},
{Type: "panel", ID: "graph", Name: "Graph", Version: "1.0.0"},
},
},
Includes: []*plugins.Includes{
{Name: "Nginx Connections", Path: "dashboards/connections.json", Type: "dashboard", Role: "Viewer", Slug: "nginx-connections"},
{Name: "Nginx Memory", Path: "dashboards/memory.json", Type: "dashboard", Role: "Viewer", Slug: "nginx-memory"},
{Name: "Nginx Panel", Type: "panel", Role: "Viewer", Slug: "nginx-panel"},
{Name: "Nginx Datasource", Type: "datasource", Role: "Viewer", Slug: "nginx-datasource"},
},
Backend: false,
},
FS: plugins.NewLocalFS(filesInDir(t, pluginDir1), pluginDir1),
Class: plugins.External,
Signature: plugins.SignatureValid,
SignatureType: plugins.GrafanaSignature,
SignatureOrg: "Grafana Labs",
Module: "plugins/test-app/module",
BaseURL: "public/plugins/test-app",
},
}
reg := fakes.NewFakePluginRegistry()
storage := fakes.NewFakePluginStorage()
procPrvdr := fakes.NewFakeBackendProcessProvider()
// Cause an initialization error
procPrvdr.BackendFactoryFunc = func(ctx context.Context, p *plugins.Plugin) backendplugin.PluginFactoryFunc {
return func(pluginID string, _ log.Logger, _ []string) (backendplugin.Plugin, error) {
if pluginID == "test-datasource" {
return nil, fmt.Errorf("failed to initialize")
}
return &fakes.FakePluginClient{}, nil
}
}
procMgr := fakes.NewFakeProcessManager()
l := newLoader(&config.Cfg{}, func(l *Loader) {
l.pluginRegistry = reg
l.pluginStorage = storage
l.processManager = procMgr
l.pluginInitializer = initializer.New(&config.Cfg{}, procPrvdr, fakes.NewFakeLicensingService())
})
got, err := l.Load(context.Background(), &fakes.FakePluginSource{
PluginClassFunc: func(ctx context.Context) plugins.Class {
return plugins.External
},
PluginURIsFunc: func(ctx context.Context) []string {
return []string{pluginDir1, pluginDir2}
},
})
require.NoError(t, err)
if !cmp.Equal(got, expected, compareOpts...) {
t.Fatalf("Result mismatch (-want +got):\n%s", cmp.Diff(got, expected, compareOpts...))
}
verifyState(t, expected, reg, procPrvdr, storage, procMgr)
})
}
func TestLoader_Load_NestedPlugins(t *testing.T) {
rootDir, err := filepath.Abs("../")
if err != nil {