Plugins: Add pluginStoreServiceLoading feature toggle (#112588)

This commit is contained in:
Todd Treece
2025-10-17 16:01:43 -04:00
committed by GitHub
parent 626b799cff
commit 69628baa9d
8 changed files with 139 additions and 35 deletions
@@ -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;
}
+8 -2
View File
@@ -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)
+8
View File
@@ -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",
},
}
)
+1
View File
@@ -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
1 Name Stage Owner requiresDevMode RequiresRestart FrontendOnly
271 cdnPluginsLoadFirst experimental @grafana/plugins-platform-backend false false false
272 cdnPluginsUrls experimental @grafana/plugins-platform-backend false false false
273 preventPanelChromeOverflow preview @grafana/grafana-frontend-platform false false true
274 pluginStoreServiceLoading experimental @grafana/plugins-platform-backend false false false
+4
View File
@@ -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"
)
+13
View File
@@ -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)
})
}
})
}