Plugins: Add pluginStoreServiceLoading feature toggle (#112588)
This commit is contained in:
@@ -1218,4 +1218,9 @@ export interface FeatureToggles {
|
||||
* @default true
|
||||
*/
|
||||
preventPanelChromeOverflow?: boolean;
|
||||
/**
|
||||
* Load plugins during store service startup instead of wire provider
|
||||
* @default false
|
||||
*/
|
||||
pluginStoreServiceLoading?: boolean;
|
||||
}
|
||||
|
||||
@@ -549,7 +549,10 @@ func Initialize(ctx context.Context, cfg *setting.Cfg, opts Options, apiOpts api
|
||||
}
|
||||
errorRegistry := pluginerrs.ProvideErrorTracker()
|
||||
loaderLoader := loader.ProvideService(pluginManagementCfg, discovery, bootstrap, validate, initialize, terminate, errorRegistry)
|
||||
pluginstoreService := pluginstore.ProvideService(inMemory, sourcesService, loaderLoader)
|
||||
pluginstoreService, err := pluginstore.ProvideService(inMemory, sourcesService, loaderLoader, featureToggles)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
filestoreService := filestore.ProvideService(inMemory)
|
||||
fileStoreManager := dashboards.ProvideFileStoreManager(pluginstoreService, filestoreService)
|
||||
folderPermissionsService, err := ossaccesscontrol.ProvideFolderPermissions(cfg, featureToggles, routeRegisterImpl, sqlStore, accessControl, ossLicensingService, folderimplService, acimplService, teamService, userService, actionSetService)
|
||||
@@ -1155,7 +1158,10 @@ func InitializeForTest(ctx context.Context, t sqlutil.ITestDB, testingT interfac
|
||||
}
|
||||
errorRegistry := pluginerrs.ProvideErrorTracker()
|
||||
loaderLoader := loader.ProvideService(pluginManagementCfg, discovery, bootstrap, validate, initialize, terminate, errorRegistry)
|
||||
pluginstoreService := pluginstore.ProvideService(inMemory, sourcesService, loaderLoader)
|
||||
pluginstoreService, err := pluginstore.ProvideService(inMemory, sourcesService, loaderLoader, featureToggles)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
filestoreService := filestore.ProvideService(inMemory)
|
||||
fileStoreManager := dashboards.ProvideFileStoreManager(pluginstoreService, filestoreService)
|
||||
folderPermissionsService, err := ossaccesscontrol.ProvideFolderPermissions(cfg, featureToggles, routeRegisterImpl, sqlStore, accessControl, ossLicensingService, folderimplService, acimplService, teamService, userService, actionSetService)
|
||||
|
||||
@@ -2111,6 +2111,14 @@ var (
|
||||
Owner: grafanaFrontendPlatformSquad,
|
||||
Expression: "true",
|
||||
},
|
||||
{
|
||||
Name: "pluginStoreServiceLoading",
|
||||
Description: "Load plugins during store service startup instead of wire provider",
|
||||
Stage: FeatureStageExperimental,
|
||||
FrontendOnly: false,
|
||||
Owner: grafanaPluginsPlatformSquad,
|
||||
Expression: "false",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -271,3 +271,4 @@ tempoSearchBackendMigration,GA,@grafana/oss-big-tent,false,true,false
|
||||
cdnPluginsLoadFirst,experimental,@grafana/plugins-platform-backend,false,false,false
|
||||
cdnPluginsUrls,experimental,@grafana/plugins-platform-backend,false,false,false
|
||||
preventPanelChromeOverflow,preview,@grafana/grafana-frontend-platform,false,false,true
|
||||
pluginStoreServiceLoading,experimental,@grafana/plugins-platform-backend,false,false,false
|
||||
|
||||
|
@@ -1093,4 +1093,8 @@ const (
|
||||
// FlagPreventPanelChromeOverflow
|
||||
// Restrict PanelChrome contents with overflow: hidden;
|
||||
FlagPreventPanelChromeOverflow = "preventPanelChromeOverflow"
|
||||
|
||||
// FlagPluginStoreServiceLoading
|
||||
// Load plugins during store service startup instead of wire provider
|
||||
FlagPluginStoreServiceLoading = "pluginStoreServiceLoading"
|
||||
)
|
||||
|
||||
@@ -2936,6 +2936,19 @@
|
||||
"expression": "false"
|
||||
}
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"name": "pluginStoreServiceLoading",
|
||||
"resourceVersion": "1760712768362",
|
||||
"creationTimestamp": "2025-10-17T14:52:48Z"
|
||||
},
|
||||
"spec": {
|
||||
"description": "Load plugins during store service startup instead of wire provider",
|
||||
"stage": "experimental",
|
||||
"codeowner": "@grafana/plugins-platform-backend",
|
||||
"expression": "false"
|
||||
}
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"name": "pluginsAutoUpdate",
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/loader"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/registry"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/sources"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
@@ -33,11 +34,36 @@ type Service struct {
|
||||
pluginRegistry registry.Service
|
||||
pluginLoader loader.Service
|
||||
pluginSources sources.Registry
|
||||
loadOnStartup bool
|
||||
}
|
||||
|
||||
func ProvideService(pluginRegistry registry.Service, pluginSources sources.Registry,
|
||||
pluginLoader loader.Service) *Service {
|
||||
return New(pluginRegistry, pluginLoader, pluginSources)
|
||||
pluginLoader loader.Service, features featuremgmt.FeatureToggles) (*Service, error) {
|
||||
if features.IsEnabledGlobally(featuremgmt.FlagPluginStoreServiceLoading) {
|
||||
s := New(pluginRegistry, pluginLoader, pluginSources)
|
||||
s.loadOnStartup = true
|
||||
return s, nil
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
start := time.Now()
|
||||
totalPlugins := 0
|
||||
logger := log.New("plugin.store")
|
||||
logger.Info("Loading plugins...")
|
||||
|
||||
for _, ps := range pluginSources.List(ctx) {
|
||||
loadedPlugins, err := pluginLoader.Load(ctx, ps)
|
||||
if err != nil {
|
||||
logger.Error("Loading plugin source failed", "source", ps.PluginClass(ctx), "error", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
totalPlugins += len(loadedPlugins)
|
||||
}
|
||||
|
||||
logger.Info("Plugins loaded", "count", totalPlugins, "duration", time.Since(start))
|
||||
|
||||
return New(pluginRegistry, pluginLoader, pluginSources), nil
|
||||
}
|
||||
|
||||
func (s *Service) Run(ctx context.Context) error {
|
||||
@@ -50,6 +76,7 @@ func (s *Service) Run(ctx context.Context) error {
|
||||
|
||||
func NewPluginStoreForTest(pluginRegistry registry.Service, pluginLoader loader.Service, pluginSources sources.Registry) (*Service, error) {
|
||||
s := New(pluginRegistry, pluginLoader, pluginSources)
|
||||
s.loadOnStartup = true
|
||||
if err := s.StartAsync(context.Background()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -70,6 +97,9 @@ func New(pluginRegistry registry.Service, pluginLoader loader.Service, pluginSou
|
||||
}
|
||||
|
||||
func (s *Service) starting(ctx context.Context) error {
|
||||
if !s.loadOnStartup {
|
||||
return nil
|
||||
}
|
||||
start := time.Now()
|
||||
totalPlugins := 0
|
||||
logger := log.New(ServiceName)
|
||||
@@ -85,7 +115,6 @@ func (s *Service) starting(ctx context.Context) error {
|
||||
}
|
||||
|
||||
logger.Info("Plugins loaded", "count", totalPlugins, "duration", time.Since(start))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -11,45 +11,83 @@ import (
|
||||
"github.com/grafana/grafana/pkg/plugins/backendplugin"
|
||||
"github.com/grafana/grafana/pkg/plugins/log"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/pluginfakes"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
)
|
||||
|
||||
func TestStore_ProvideService(t *testing.T) {
|
||||
t.Run("Plugin sources are added in order", func(t *testing.T) {
|
||||
var loadedSrcs []plugins.Class
|
||||
l := &pluginfakes.FakeLoader{
|
||||
LoadFunc: func(ctx context.Context, src plugins.PluginSource) ([]*plugins.Plugin, error) {
|
||||
loadedSrcs = append(loadedSrcs, src.PluginClass(ctx))
|
||||
return nil, nil
|
||||
tests := []struct {
|
||||
name string
|
||||
featureEnabled bool
|
||||
expectedLoadOnStartup bool
|
||||
expectedBeforeStart []plugins.Class
|
||||
expectedAfterStart []plugins.Class
|
||||
}{
|
||||
{
|
||||
name: "with FlagPluginStoreServiceLoading disabled",
|
||||
featureEnabled: false,
|
||||
expectedLoadOnStartup: false,
|
||||
expectedBeforeStart: []plugins.Class{"1", "2", "3"},
|
||||
expectedAfterStart: []plugins.Class{"1", "2", "3"},
|
||||
},
|
||||
{
|
||||
name: "with FlagPluginStoreServiceLoading enabled",
|
||||
featureEnabled: true,
|
||||
expectedLoadOnStartup: true,
|
||||
expectedBeforeStart: nil,
|
||||
expectedAfterStart: []plugins.Class{"1", "2", "3"},
|
||||
},
|
||||
}
|
||||
|
||||
srcs := &pluginfakes.FakeSourceRegistry{ListFunc: func(_ context.Context) []plugins.PluginSource {
|
||||
return []plugins.PluginSource{
|
||||
&pluginfakes.FakePluginSource{
|
||||
PluginClassFunc: func(ctx context.Context) plugins.Class {
|
||||
return "1"
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var loadedSrcs []plugins.Class
|
||||
l := &pluginfakes.FakeLoader{
|
||||
LoadFunc: func(ctx context.Context, src plugins.PluginSource) ([]*plugins.Plugin, error) {
|
||||
loadedSrcs = append(loadedSrcs, src.PluginClass(ctx))
|
||||
return nil, nil
|
||||
},
|
||||
},
|
||||
&pluginfakes.FakePluginSource{
|
||||
PluginClassFunc: func(ctx context.Context) plugins.Class {
|
||||
return "2"
|
||||
},
|
||||
},
|
||||
&pluginfakes.FakePluginSource{
|
||||
PluginClassFunc: func(ctx context.Context) plugins.Class {
|
||||
return "3"
|
||||
},
|
||||
},
|
||||
}
|
||||
}}
|
||||
}
|
||||
|
||||
service := ProvideService(pluginfakes.NewFakePluginRegistry(), srcs, l)
|
||||
ctx := context.Background()
|
||||
err := service.StartAsync(ctx)
|
||||
require.NoError(t, err)
|
||||
err = service.AwaitRunning(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []plugins.Class{"1", "2", "3"}, loadedSrcs)
|
||||
srcs := &pluginfakes.FakeSourceRegistry{ListFunc: func(_ context.Context) []plugins.PluginSource {
|
||||
return []plugins.PluginSource{
|
||||
&pluginfakes.FakePluginSource{
|
||||
PluginClassFunc: func(ctx context.Context) plugins.Class {
|
||||
return "1"
|
||||
},
|
||||
},
|
||||
&pluginfakes.FakePluginSource{
|
||||
PluginClassFunc: func(ctx context.Context) plugins.Class {
|
||||
return "2"
|
||||
},
|
||||
},
|
||||
&pluginfakes.FakePluginSource{
|
||||
PluginClassFunc: func(ctx context.Context) plugins.Class {
|
||||
return "3"
|
||||
},
|
||||
},
|
||||
}
|
||||
}}
|
||||
|
||||
var features featuremgmt.FeatureToggles
|
||||
if tt.featureEnabled {
|
||||
features = featuremgmt.WithFeatures(featuremgmt.FlagPluginStoreServiceLoading)
|
||||
} else {
|
||||
features = featuremgmt.WithFeatures()
|
||||
}
|
||||
|
||||
service, err := ProvideService(pluginfakes.NewFakePluginRegistry(), srcs, l, features)
|
||||
require.Equal(t, tt.expectedLoadOnStartup, service.loadOnStartup)
|
||||
require.Equal(t, tt.expectedBeforeStart, loadedSrcs)
|
||||
require.NoError(t, err)
|
||||
ctx := context.Background()
|
||||
err = service.StartAsync(ctx)
|
||||
require.NoError(t, err)
|
||||
err = service.AwaitRunning(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tt.expectedAfterStart, loadedSrcs)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user