Plugins: Move store init to dskit service (#111823)

This commit is contained in:
Todd Treece
2025-10-02 19:53:31 -04:00
committed by GitHub
parent 5798181fb0
commit 2d232aa10d
16 changed files with 394 additions and 222 deletions
+72 -71
View File
@@ -7,6 +7,7 @@ import (
"path/filepath"
"sync"
"github.com/grafana/dskit/services"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing"
@@ -39,6 +40,8 @@ import (
"github.com/grafana/grafana/pkg/storage/legacysql/dualwrite"
)
const ServiceName = "provisioning"
func ProvideService(
ac accesscontrol.AccessControl,
cfg *setting.Cfg,
@@ -91,6 +94,8 @@ func ProvideService(
dual: dual,
}
s.NamedService = services.NewBasicService(s.starting, s.running, nil).WithName(ServiceName)
if err := s.setDashboardProvisioner(); err != nil {
return nil, err
}
@@ -98,6 +103,67 @@ func ProvideService(
return s, nil
}
func (ps *ProvisioningServiceImpl) starting(ctx context.Context) error {
if err := ps.ProvisionDatasources(ctx); err != nil {
ps.log.Error("Failed to provision data sources", "error", err)
return err
}
if err := ps.ProvisionPlugins(ctx); err != nil {
ps.log.Error("Failed to provision plugins", "error", err)
return err
}
if err := ps.ProvisionAlerting(ctx); err != nil {
ps.log.Error("Failed to provision alerting", "error", err)
return err
}
// Migrating prom types relies on data source provisioning to already be completed
// If we can make services depend on other services completing first,
// then we should remove this from provisioning
if err := ps.migratePrometheusType(ctx); err != nil {
ps.log.Error("Failed to migrate Prometheus type", "error", err)
return err
}
if err := ps.ProvisionDashboards(ctx); err != nil {
ps.log.Error("Failed to provision dashboard", "error", err)
// Consider the allow list of errors for which running the provisioning service should not
// fail. For now this includes only dashboards.ErrGetOrCreateFolder.
if !errors.Is(err, dashboards.ErrGetOrCreateFolder) {
return err
}
}
if ps.dashboardProvisioner.HasDashboardSources() {
ps.searchService.TriggerReIndex()
}
return nil
}
func (ps *ProvisioningServiceImpl) running(ctx context.Context) error {
for {
// Wait for unlock. This is tied to new dashboardProvisioner to be instantiated before we start polling.
ps.mutex.Lock()
// Using background here because otherwise if root context was canceled the select later on would
// non-deterministically take one of the route possibly going into one polling loop before exiting.
pollingContext, cancelFun := context.WithCancel(context.Background())
ps.pollingCtxCancel = cancelFun
ps.dashboardProvisioner.PollChanges(pollingContext)
ps.mutex.Unlock()
select {
case <-pollingContext.Done():
// Polling was canceled.
continue
case <-ctx.Done():
// Root server context was cancelled so cancel polling and leave.
ps.cancelPolling()
return nil
}
}
}
func (ps *ProvisioningServiceImpl) setDashboardProvisioner() error {
dashboardPath := filepath.Join(ps.Cfg.ProvisioningPath, "dashboards")
dashProvisioner, err := ps.newDashboardProvisioner(context.Background(), dashboardPath, ps.dashboardProvisioningService, ps.orgService, ps.dashboardService, ps.folderService, ps.dual)
@@ -137,6 +203,8 @@ func newProvisioningServiceImpl(
migratePrometheusType: migratePrometheusType,
}
s.NamedService = services.NewBasicService(s.starting, s.running, nil).WithName(ServiceName)
if err := s.setDashboardProvisioner(); err != nil {
return nil, err
}
@@ -145,6 +213,7 @@ func newProvisioningServiceImpl(
}
type ProvisioningServiceImpl struct {
services.NamedService
Cfg *setting.Cfg
SQLStore db.DB
orgService org.Service
@@ -173,7 +242,6 @@ type ProvisioningServiceImpl struct {
resourcePermissions accesscontrol.ReceiverPermissionsService
tracer tracing.Tracer
dual dualwrite.Service
onceInitProvisioners sync.Once
migratePrometheusType func(context.Context) error
}
@@ -185,78 +253,11 @@ func (ps *ProvisioningServiceImpl) RunInitProvisioners(ctx context.Context) erro
}
func (ps *ProvisioningServiceImpl) Run(ctx context.Context) error {
var err error
// Run Datasources, Plugins and Alerting Provisioning only once.
// It can't be initialized at RunInitProvisioners because it
// depends on the /apis endpoints to be already running and listeningq
ps.onceInitProvisioners.Do(func() {
err = ps.ProvisionDatasources(ctx)
if err != nil {
ps.log.Error("Failed to provision data sources", "error", err)
return
}
err = ps.ProvisionPlugins(ctx)
if err != nil {
ps.log.Error("Failed to provision plugins", "error", err)
return
}
err = ps.ProvisionAlerting(ctx)
if err != nil {
ps.log.Error("Failed to provision alerting", "error", err)
return
}
// Migrating prom types relies on data source provisioning to already be completed
// If we can make services depend on other services completing first,
// then we should remove this from provisioning
err = ps.migratePrometheusType(ctx)
if err != nil {
ps.log.Error("Failed to migrate Prometheus type", "error", err)
return
}
})
if err != nil {
// error already logged
if err := ps.StartAsync(ctx); err != nil {
return err
}
err = ps.ProvisionDashboards(ctx)
if err != nil {
ps.log.Error("Failed to provision dashboard", "error", err)
// Consider the allow list of errors for which running the provisioning service should not
// fail. For now this includes only dashboards.ErrGetOrCreateFolder.
if !errors.Is(err, dashboards.ErrGetOrCreateFolder) {
return err
}
}
if ps.dashboardProvisioner.HasDashboardSources() {
ps.searchService.TriggerReIndex()
}
for {
// Wait for unlock. This is tied to new dashboardProvisioner to be instantiated before we start polling.
ps.mutex.Lock()
// Using background here because otherwise if root context was canceled the select later on would
// non-deterministically take one of the route possibly going into one polling loop before exiting.
pollingContext, cancelFun := context.WithCancel(context.Background())
ps.pollingCtxCancel = cancelFun
ps.dashboardProvisioner.PollChanges(pollingContext)
ps.mutex.Unlock()
select {
case <-pollingContext.Done():
// Polling was canceled.
continue
case <-ctx.Done():
// Root server context was cancelled so cancel polling and leave.
ps.cancelPolling()
return ctx.Err()
}
}
stopCtx := context.Background()
return ps.AwaitTerminated(stopCtx)
}
func (ps *ProvisioningServiceImpl) ProvisionDatasources(ctx context.Context) error {
@@ -48,7 +48,7 @@ func TestProvisioningServiceImpl(t *testing.T) {
serviceTest.waitForStop()
assert.False(t, serviceTest.serviceRunning, "Service should not be running")
assert.Equal(t, context.Canceled, serviceTest.serviceError, "Service should have returned canceled error")
assert.NoError(t, serviceTest.serviceError, "Service should not have returned an error")
})
t.Run("Failed reloading does not stop polling with old provisioned", func(t *testing.T) {
@@ -91,7 +91,7 @@ func TestProvisioningServiceImpl(t *testing.T) {
serviceTest.cancel()
serviceTest.waitForStop()
assert.Equal(t, context.Canceled, serviceTest.serviceError)
assert.NoError(t, serviceTest.serviceError, "Service should not have returned an error")
})
t.Run("Should return run error when dashboard provisioning fails for non-allow-listed error", func(t *testing.T) {