Prometheus data source: Migration service (#107364)
* copying from secrets migration * service runs and mig promds type * creating data source check * adding aws * split into azure/aws service. feature flag. auto install * add tests * clean up * lint * add code owner * imporvments from andres * remove prom mig from http_server * remove interface for testing * add prom mig to provisining data sources so prov happens before mig * fit into prov * comment * log debug instead of returning in update type * Trigger Build * feature flag being weird * not public method * copying from secrets migration * service runs and mig promds type * creating data source check * adding aws * add tests * clean up * imporvments from andres * remove prom mig from http_server * remove interface for testing * add prom mig to provisining data sources so prov happens before mig * fit into prov * Trigger Build * not public method * remove logger import
This commit is contained in:
@@ -1137,6 +1137,8 @@ eslint-suppressions.json @grafanabot
|
||||
# Feature toggles
|
||||
/pkg/services/featuremgmt/ @grafana/grafana-backend-services-squad
|
||||
|
||||
# Data source migrations
|
||||
/pkg/services/promtypemigration/ @grafana/partner-datasources @grafana/aws-datasources
|
||||
|
||||
# Kind definitions
|
||||
/kinds/dashboard @grafana/dashboards-squad
|
||||
|
||||
@@ -1132,4 +1132,9 @@ export interface FeatureToggles {
|
||||
* @default false
|
||||
*/
|
||||
azureResourcePickerUpdates?: boolean;
|
||||
/**
|
||||
* Checks for deprecated Prometheus authentication methods (SigV4 and Azure), installs the relevant data source, and migrates the Prometheus data sources
|
||||
* @default false
|
||||
*/
|
||||
prometheusTypeMigration?: boolean;
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginexternal"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/plugininstaller"
|
||||
pluginStore "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
|
||||
"github.com/grafana/grafana/pkg/services/promtypemigration"
|
||||
"github.com/grafana/grafana/pkg/services/provisioning"
|
||||
publicdashboardsmetric "github.com/grafana/grafana/pkg/services/publicdashboards/metric"
|
||||
"github.com/grafana/grafana/pkg/services/rendering"
|
||||
@@ -71,6 +72,7 @@ func ProvideBackgroundServiceRegistry(
|
||||
pluginDashboardUpdater *plugindashboardsservice.DashboardUpdater,
|
||||
dashboardServiceImpl *service.DashboardServiceImpl,
|
||||
secretsGarbageCollectionWorker *secretsgarbagecollectionworker.Worker,
|
||||
promTypeMigrationProvider promtypemigration.PromTypeMigrationProvider,
|
||||
// Need to make sure these are initialized, is there a better place to put them?
|
||||
_ dashboardsnapshots.Service,
|
||||
_ serviceaccounts.Service,
|
||||
@@ -118,6 +120,7 @@ func ProvideBackgroundServiceRegistry(
|
||||
pluginDashboardUpdater,
|
||||
dashboardServiceImpl,
|
||||
secretsGarbageCollectionWorker,
|
||||
promTypeMigrationProvider,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -125,6 +125,7 @@ import (
|
||||
pluginDashboards "github.com/grafana/grafana/pkg/services/pluginsintegration/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginaccesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/preference/prefimpl"
|
||||
promTypeMigration "github.com/grafana/grafana/pkg/services/promtypemigration"
|
||||
"github.com/grafana/grafana/pkg/services/publicdashboards"
|
||||
publicdashboardsApi "github.com/grafana/grafana/pkg/services/publicdashboards/api"
|
||||
publicdashboardsStore "github.com/grafana/grafana/pkg/services/publicdashboards/database"
|
||||
@@ -392,6 +393,10 @@ var wireBasicSet = wire.NewSet(
|
||||
secretsMigrations.ProvideDataSourceMigrationService,
|
||||
secretsMigrations.ProvideSecretMigrationProvider,
|
||||
wire.Bind(new(secretsMigrations.SecretMigrationProvider), new(*secretsMigrations.SecretMigrationProviderImpl)),
|
||||
promTypeMigration.ProvideAzurePromMigrationService,
|
||||
promTypeMigration.ProvideAmazonPromMigrationService,
|
||||
promTypeMigration.ProvidePromTypeMigrationProvider,
|
||||
wire.Bind(new(promTypeMigration.PromTypeMigrationProvider), new(*promTypeMigration.PromTypeMigrationProviderImpl)),
|
||||
resourcepermissions.NewActionSetService,
|
||||
wire.Bind(new(accesscontrol.ActionResolver), new(resourcepermissions.ActionSetService)),
|
||||
wire.Bind(new(pluginaccesscontrol.ActionSetRegistry), new(resourcepermissions.ActionSetService)),
|
||||
|
||||
+12
-5
File diff suppressed because one or more lines are too long
@@ -1964,6 +1964,14 @@ var (
|
||||
Owner: grafanaPartnerPluginsSquad,
|
||||
Expression: "false",
|
||||
},
|
||||
{
|
||||
Name: "prometheusTypeMigration",
|
||||
Description: "Checks for deprecated Prometheus authentication methods (SigV4 and Azure), installs the relevant data source, and migrates the Prometheus data sources",
|
||||
Stage: FeatureStageExperimental,
|
||||
RequiresRestart: true,
|
||||
Owner: grafanaPartnerPluginsSquad,
|
||||
Expression: "false",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -252,3 +252,4 @@ teamFolders,experimental,@grafana/grafana-search-navigate-organise,false,false,f
|
||||
alertingTriage,experimental,@grafana/alerting-squad,false,false,true
|
||||
graphiteBackendMode,privatePreview,@grafana/partner-datasources,false,false,false
|
||||
azureResourcePickerUpdates,preview,@grafana/partner-datasources,false,false,true
|
||||
prometheusTypeMigration,experimental,@grafana/partner-datasources,false,true,false
|
||||
|
||||
|
@@ -1018,4 +1018,8 @@ const (
|
||||
// FlagAzureResourcePickerUpdates
|
||||
// Enables the updated Azure Monitor resource picker
|
||||
FlagAzureResourcePickerUpdates = "azureResourcePickerUpdates"
|
||||
|
||||
// FlagPrometheusTypeMigration
|
||||
// Checks for deprecated Prometheus authentication methods (SigV4 and Azure), installs the relevant data source, and migrates the Prometheus data sources
|
||||
FlagPrometheusTypeMigration = "prometheusTypeMigration"
|
||||
)
|
||||
|
||||
@@ -2714,6 +2714,23 @@
|
||||
"frontend": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"name": "prometheusTypeMigration",
|
||||
"resourceVersion": "1757089774247",
|
||||
"creationTimestamp": "2025-08-25T21:53:16Z",
|
||||
"annotations": {
|
||||
"grafana.app/updatedTimestamp": "2025-09-05 16:29:34.247055837 +0000 UTC"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"description": "Checks for deprecated Prometheus authentication methods (SigV4 and Azure), installs the relevant data source, and migrates the Prometheus data sources",
|
||||
"stage": "experimental",
|
||||
"codeowner": "@grafana/partner-datasources",
|
||||
"requiresRestart": true,
|
||||
"expression": "false"
|
||||
}
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"name": "provisioning",
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package promtypemigration
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/registry"
|
||||
"github.com/grafana/grafana/pkg/plugins/repo"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
type AmazonPromMigrationService struct {
|
||||
promMigrationService
|
||||
}
|
||||
|
||||
func ProvideAmazonPromMigrationService(
|
||||
dataSourcesService datasources.DataSourceService,
|
||||
pluginRegistry registry.Service,
|
||||
pluginRepo repo.Service,
|
||||
pluginInstaller plugins.Installer,
|
||||
cfg *setting.Cfg,
|
||||
) *AmazonPromMigrationService {
|
||||
return &AmazonPromMigrationService{
|
||||
promMigrationService: promMigrationService{
|
||||
dataSourcesService: dataSourcesService,
|
||||
pluginRegistry: pluginRegistry,
|
||||
pluginRepo: pluginRepo,
|
||||
pluginInstaller: pluginInstaller,
|
||||
cfg: cfg,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *AmazonPromMigrationService) getPrometheusDataSources(ctx context.Context) ([]*datasources.DataSource, error) {
|
||||
amazonPromDs := []*datasources.DataSource{}
|
||||
query := &datasources.GetDataSourcesByTypeQuery{
|
||||
Type: datasources.DS_PROMETHEUS,
|
||||
}
|
||||
dsList, err := s.dataSourcesService.GetDataSourcesByType(ctx, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, ds := range dsList {
|
||||
if sigV4Auth, found := ds.JsonData.CheckGet("sigV4Auth"); found {
|
||||
if enabled, err := sigV4Auth.Bool(); err != nil || !enabled {
|
||||
continue
|
||||
}
|
||||
amazonPromDs = append(amazonPromDs, ds)
|
||||
continue
|
||||
}
|
||||
}
|
||||
return amazonPromDs, nil
|
||||
}
|
||||
|
||||
func (s *AmazonPromMigrationService) Migrate(ctx context.Context) error {
|
||||
pds, err := s.getPrometheusDataSources(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.applyMigration(ctx, datasources.DS_AMAZON_PROMETHEUS, pds)
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package promtypemigration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetPrometheusDataSources_Amazon_ReturnsOnlyAmazonPrometheus(t *testing.T) {
|
||||
ds1 := &datasources.DataSource{
|
||||
JsonData: simplejson.NewFromAny(map[string]any{
|
||||
"sigV4Auth": true,
|
||||
}),
|
||||
}
|
||||
ds2 := &datasources.DataSource{
|
||||
JsonData: simplejson.NewFromAny(map[string]any{
|
||||
"sigV4Auth": false,
|
||||
}),
|
||||
}
|
||||
ds3 := &datasources.DataSource{
|
||||
JsonData: simplejson.NewFromAny(map[string]any{
|
||||
"sigV4Auth": true,
|
||||
}),
|
||||
}
|
||||
ds4 := &datasources.DataSource{
|
||||
JsonData: simplejson.NewFromAny(map[string]any{
|
||||
"sigV4Auth": nil,
|
||||
}),
|
||||
}
|
||||
mock := &mockDataSourcesService{
|
||||
dataSources: []*datasources.DataSource{ds1, ds2, ds3, ds4},
|
||||
}
|
||||
svc := &AmazonPromMigrationService{
|
||||
promMigrationService: promMigrationService{
|
||||
dataSourcesService: mock,
|
||||
},
|
||||
}
|
||||
|
||||
got, err := svc.getPrometheusDataSources(context.Background())
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, got, 2)
|
||||
assert.Contains(t, got, ds1)
|
||||
assert.Contains(t, got, ds3)
|
||||
}
|
||||
|
||||
func TestGetPrometheusDataSources_Amazon_ErrorFromService(t *testing.T) {
|
||||
mockSvc := &mockDataSourcesService{
|
||||
err: errors.New("service error"),
|
||||
}
|
||||
svc := &AmazonPromMigrationService{
|
||||
promMigrationService: promMigrationService{
|
||||
dataSourcesService: mockSvc,
|
||||
},
|
||||
}
|
||||
|
||||
got, err := svc.getPrometheusDataSources(context.Background())
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, got)
|
||||
}
|
||||
|
||||
func TestGetPrometheusDataSources_Amazon_NoSigV4Auth(t *testing.T) {
|
||||
ds := &datasources.DataSource{
|
||||
JsonData: simplejson.NewFromAny(map[string]any{}),
|
||||
}
|
||||
mockSvc := &mockDataSourcesService{
|
||||
dataSources: []*datasources.DataSource{ds},
|
||||
}
|
||||
svc := &AmazonPromMigrationService{
|
||||
promMigrationService: promMigrationService{
|
||||
dataSourcesService: mockSvc,
|
||||
},
|
||||
}
|
||||
|
||||
got, err := svc.getPrometheusDataSources(context.Background())
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, got)
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package promtypemigration
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/registry"
|
||||
"github.com/grafana/grafana/pkg/plugins/repo"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
type AzurePromMigrationService struct {
|
||||
promMigrationService
|
||||
}
|
||||
|
||||
func ProvideAzurePromMigrationService(
|
||||
dataSourcesService datasources.DataSourceService,
|
||||
pluginRegistry registry.Service,
|
||||
pluginRepo repo.Service,
|
||||
pluginInstaller plugins.Installer,
|
||||
cfg *setting.Cfg,
|
||||
) *AzurePromMigrationService {
|
||||
return &AzurePromMigrationService{
|
||||
promMigrationService: promMigrationService{
|
||||
dataSourcesService: dataSourcesService,
|
||||
pluginRegistry: pluginRegistry,
|
||||
pluginRepo: pluginRepo,
|
||||
pluginInstaller: pluginInstaller,
|
||||
cfg: cfg,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *AzurePromMigrationService) getPrometheusDataSources(ctx context.Context) ([]*datasources.DataSource, error) {
|
||||
azurePromDs := []*datasources.DataSource{}
|
||||
query := &datasources.GetDataSourcesByTypeQuery{
|
||||
Type: datasources.DS_PROMETHEUS,
|
||||
}
|
||||
dsList, err := s.dataSourcesService.GetDataSourcesByType(ctx, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, ds := range dsList {
|
||||
if azureAuth, found := ds.JsonData.CheckGet("azureCredentials"); found {
|
||||
var val any
|
||||
if val, err = azureAuth.Value(); err != nil || val == nil {
|
||||
continue
|
||||
}
|
||||
azurePromDs = append(azurePromDs, ds)
|
||||
continue
|
||||
}
|
||||
}
|
||||
return azurePromDs, nil
|
||||
}
|
||||
|
||||
func (s *AzurePromMigrationService) Migrate(ctx context.Context) error {
|
||||
pds, err := s.getPrometheusDataSources(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.applyMigration(ctx, datasources.DS_AZURE_PROMETHEUS, pds)
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package promtypemigration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetPrometheusDataSources_Azure_ReturnsOnlyAzurePrometheus(t *testing.T) {
|
||||
ds1 := &datasources.DataSource{
|
||||
JsonData: simplejson.NewFromAny(map[string]any{
|
||||
"azureCredentials": []any{},
|
||||
}),
|
||||
}
|
||||
ds2 := &datasources.DataSource{
|
||||
JsonData: simplejson.NewFromAny(map[string]any{}),
|
||||
}
|
||||
ds3 := &datasources.DataSource{
|
||||
JsonData: simplejson.NewFromAny(map[string]any{
|
||||
"azureCredentials": []any{},
|
||||
}),
|
||||
}
|
||||
ds4 := &datasources.DataSource{
|
||||
JsonData: simplejson.NewFromAny(map[string]any{}),
|
||||
}
|
||||
mock := &mockDataSourcesService{
|
||||
dataSources: []*datasources.DataSource{ds1, ds2, ds3, ds4},
|
||||
}
|
||||
svc := &AzurePromMigrationService{
|
||||
promMigrationService: promMigrationService{
|
||||
dataSourcesService: mock,
|
||||
},
|
||||
}
|
||||
|
||||
got, err := svc.getPrometheusDataSources(context.Background())
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, got, 2)
|
||||
assert.Contains(t, got, ds1)
|
||||
assert.Contains(t, got, ds3)
|
||||
}
|
||||
|
||||
func TestGetPrometheusDataSources_Azure_ErrorFromService(t *testing.T) {
|
||||
mockSvc := &mockDataSourcesService{
|
||||
err: errors.New("service error"),
|
||||
}
|
||||
svc := &AzurePromMigrationService{
|
||||
promMigrationService: promMigrationService{
|
||||
dataSourcesService: mockSvc,
|
||||
},
|
||||
}
|
||||
|
||||
got, err := svc.getPrometheusDataSources(context.Background())
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, got)
|
||||
}
|
||||
|
||||
func TestGetPrometheusDataSources_Azure_NoAzureAuth(t *testing.T) {
|
||||
ds := &datasources.DataSource{
|
||||
JsonData: simplejson.NewFromAny(map[string]any{}),
|
||||
}
|
||||
mockSvc := &mockDataSourcesService{
|
||||
dataSources: []*datasources.DataSource{ds},
|
||||
}
|
||||
svc := &AzurePromMigrationService{
|
||||
promMigrationService: promMigrationService{
|
||||
dataSourcesService: mockSvc,
|
||||
},
|
||||
}
|
||||
|
||||
got, err := svc.getPrometheusDataSources(context.Background())
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, got)
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package promtypemigration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/infra/serverlock"
|
||||
"github.com/grafana/grafana/pkg/registry"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
)
|
||||
|
||||
var logger = log.New("promds.migration")
|
||||
|
||||
const actionName = "prom type migration task"
|
||||
|
||||
type PromTypeMigrationService interface {
|
||||
Migrate(ctx context.Context) error
|
||||
}
|
||||
|
||||
type PromTypeMigrationProvider interface {
|
||||
registry.BackgroundService
|
||||
}
|
||||
|
||||
type PromTypeMigrationProviderImpl struct {
|
||||
services []PromTypeMigrationService
|
||||
features featuremgmt.FeatureToggles
|
||||
ServerLockService *serverlock.ServerLockService
|
||||
}
|
||||
|
||||
func ProvidePromTypeMigrationProvider(
|
||||
serverLockService *serverlock.ServerLockService,
|
||||
features featuremgmt.FeatureToggles,
|
||||
promAzureAuthMigrationService *AzurePromMigrationService,
|
||||
promAmazonAuthMigrationService *AmazonPromMigrationService,
|
||||
) *PromTypeMigrationProviderImpl {
|
||||
return &PromTypeMigrationProviderImpl{
|
||||
ServerLockService: serverLockService,
|
||||
features: features,
|
||||
services: []PromTypeMigrationService{promAzureAuthMigrationService, promAmazonAuthMigrationService},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *PromTypeMigrationProviderImpl) Run(ctx context.Context) error {
|
||||
if !s.features.IsEnabled(ctx, featuremgmt.FlagPrometheusTypeMigration) {
|
||||
return nil
|
||||
}
|
||||
return s.migrate(ctx)
|
||||
}
|
||||
|
||||
// migrate Run migration services. This will block until all services have exited.
|
||||
// This should only be called once at startup
|
||||
func (s *PromTypeMigrationProviderImpl) migrate(ctx context.Context) error {
|
||||
err := s.ServerLockService.LockExecuteAndRelease(ctx, actionName, time.Minute*10, func(context.Context) {
|
||||
for _, service := range s.services {
|
||||
serviceName := reflect.TypeOf(service).String()
|
||||
logger.Debug("Starting prom data source type migration service", "service", serviceName)
|
||||
err := service.Migrate(ctx)
|
||||
if err != nil {
|
||||
logger.Error("Stopped prom data source type migration service", "service", serviceName, "reason", err)
|
||||
}
|
||||
logger.Debug("Finished prom data source type migration service", "service", serviceName)
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
logger.Error("Server lock for prom data source type migration already exists")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package promtypemigration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"runtime"
|
||||
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/registry"
|
||||
"github.com/grafana/grafana/pkg/plugins/repo"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
type PromMigrationHandler interface {
|
||||
Migrate(context.Context, *promMigrationService) error
|
||||
}
|
||||
|
||||
type promMigrationService struct {
|
||||
cfg *setting.Cfg
|
||||
dataSourcesService datasources.DataSourceService
|
||||
pluginRegistry registry.Service
|
||||
pluginRepo repo.Service
|
||||
pluginInstaller plugins.Installer
|
||||
}
|
||||
|
||||
func (s *promMigrationService) applyMigration(ctx context.Context, pluginID string, promDataSources []*datasources.DataSource) error {
|
||||
if len(promDataSources) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// check to see if prom is installed, if not install it
|
||||
if _, installed := s.pluginRegistry.Plugin(ctx, pluginID, ""); !installed {
|
||||
compatOpts := plugins.NewAddOpts(s.cfg.BuildVersion, runtime.GOOS, runtime.GOARCH, "")
|
||||
err := s.pluginInstaller.Add(ctx, pluginID, "", compatOpts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
logger.Debug("performing prometheus data source type migration", "plugin", pluginID)
|
||||
|
||||
for _, ds := range promDataSources {
|
||||
err := s.updateDataSourceType(ctx, ds, pluginID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
logger.Debug("prometheus data source type migration complete", "plugin", pluginID)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *promMigrationService) updateDataSourceType(ctx context.Context, ds *datasources.DataSource, newType string) error {
|
||||
secureJsonData, err := s.dataSourcesService.DecryptedValues(ctx, ds)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ds.JsonData == nil {
|
||||
logger.Debug("no JsonData found", "data source ID", ds.ID)
|
||||
ds.JsonData = &simplejson.Json{}
|
||||
}
|
||||
ds.JsonData.Set("prometheus-type-migration", true)
|
||||
_, err = s.dataSourcesService.UpdateDataSource(ctx, &datasources.UpdateDataSourceCommand{
|
||||
ID: ds.ID,
|
||||
Type: newType,
|
||||
OrgID: ds.OrgID,
|
||||
UID: ds.UID,
|
||||
Name: ds.Name,
|
||||
JsonData: ds.JsonData,
|
||||
SecureJsonData: secureJsonData,
|
||||
|
||||
// These are needed by the SQL function due to UseBool and MustCols
|
||||
IsDefault: ds.IsDefault,
|
||||
BasicAuth: ds.BasicAuth,
|
||||
WithCredentials: ds.WithCredentials,
|
||||
ReadOnly: ds.ReadOnly,
|
||||
User: ds.User,
|
||||
Database: ds.Database,
|
||||
})
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package promtypemigration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// Mocks
|
||||
|
||||
type mockPluginRegistry struct {
|
||||
installed bool
|
||||
}
|
||||
|
||||
func (m *mockPluginRegistry) Plugin(ctx context.Context, id string, _ string) (*plugins.Plugin, bool) {
|
||||
if m.installed {
|
||||
return &plugins.Plugin{}, true
|
||||
}
|
||||
return &plugins.Plugin{}, false
|
||||
}
|
||||
func (m *mockPluginRegistry) Plugins(ctx context.Context) []*plugins.Plugin { return nil }
|
||||
func (m *mockPluginRegistry) Add(ctx context.Context, plugin *plugins.Plugin) error { return nil }
|
||||
func (m *mockPluginRegistry) Remove(ctx context.Context, id, version string) error { return nil }
|
||||
|
||||
type mockPluginInstaller struct {
|
||||
addCalled bool
|
||||
addErr error
|
||||
}
|
||||
|
||||
func (m *mockPluginInstaller) Add(ctx context.Context, pluginID, version string, opts plugins.AddOpts) error {
|
||||
m.addCalled = true
|
||||
return m.addErr
|
||||
}
|
||||
func (m *mockPluginInstaller) Remove(ctx context.Context, pluginID, version string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type mockDataSourcesService struct {
|
||||
datasources.DataSourceService
|
||||
dataSources []*datasources.DataSource
|
||||
err error
|
||||
}
|
||||
|
||||
func (m *mockDataSourcesService) DecryptedValues(ctx context.Context, ds *datasources.DataSource) (map[string]string, error) {
|
||||
return map[string]string{}, nil
|
||||
}
|
||||
|
||||
func (m *mockDataSourcesService) UpdateDataSource(ctx context.Context, cmd *datasources.UpdateDataSourceCommand) (*datasources.DataSource, error) {
|
||||
return &datasources.DataSource{}, m.err
|
||||
}
|
||||
|
||||
func (m *mockDataSourcesService) GetDataSourcesByType(ctx context.Context, query *datasources.GetDataSourcesByTypeQuery) ([]*datasources.DataSource, error) {
|
||||
return m.dataSources, m.err
|
||||
}
|
||||
|
||||
// Test cases
|
||||
|
||||
func TestApplyMigration_NoDataSources(t *testing.T) {
|
||||
svc := &promMigrationService{}
|
||||
err := svc.applyMigration(context.Background(), "prometheus", []*datasources.DataSource{})
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestApplyMigration_PluginAlreadyInstalled(t *testing.T) {
|
||||
ds := &datasources.DataSource{ID: 1, JsonData: simplejson.New()}
|
||||
svc := &promMigrationService{
|
||||
cfg: &setting.Cfg{BuildVersion: "1.0"},
|
||||
dataSourcesService: &mockDataSourcesService{},
|
||||
pluginRegistry: &mockPluginRegistry{installed: true},
|
||||
pluginInstaller: &mockPluginInstaller{},
|
||||
}
|
||||
err := svc.applyMigration(context.Background(), "prometheus", []*datasources.DataSource{ds})
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestApplyMigration_PluginNotInstalled_InstallSucceeds(t *testing.T) {
|
||||
ds := &datasources.DataSource{ID: 1, JsonData: simplejson.New()}
|
||||
installer := &mockPluginInstaller{}
|
||||
svc := &promMigrationService{
|
||||
cfg: &setting.Cfg{BuildVersion: "1.0"},
|
||||
dataSourcesService: &mockDataSourcesService{},
|
||||
pluginRegistry: &mockPluginRegistry{installed: false},
|
||||
pluginInstaller: installer,
|
||||
}
|
||||
err := svc.applyMigration(context.Background(), "prometheus", []*datasources.DataSource{ds})
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, installer.addCalled)
|
||||
}
|
||||
|
||||
func TestApplyMigration_PluginNotInstalled_InstallFails(t *testing.T) {
|
||||
ds := &datasources.DataSource{ID: 1}
|
||||
installer := &mockPluginInstaller{addErr: errors.New("install failed")}
|
||||
svc := &promMigrationService{
|
||||
cfg: &setting.Cfg{BuildVersion: "1.0"},
|
||||
dataSourcesService: &mockDataSourcesService{},
|
||||
pluginRegistry: &mockPluginRegistry{installed: false},
|
||||
pluginInstaller: installer,
|
||||
}
|
||||
err := svc.applyMigration(context.Background(), "prometheus", []*datasources.DataSource{ds})
|
||||
assert.EqualError(t, err, "install failed")
|
||||
}
|
||||
|
||||
func TestApplyMigration_UpdateDataSourceFails(t *testing.T) {
|
||||
ds := &datasources.DataSource{ID: 1, JsonData: simplejson.New()}
|
||||
dataSvc := &mockDataSourcesService{err: errors.New("update failed")}
|
||||
svc := &promMigrationService{
|
||||
cfg: &setting.Cfg{BuildVersion: "1.0"},
|
||||
dataSourcesService: dataSvc,
|
||||
pluginRegistry: &mockPluginRegistry{installed: true},
|
||||
pluginInstaller: &mockPluginInstaller{},
|
||||
}
|
||||
err := svc.applyMigration(context.Background(), "prometheus", []*datasources.DataSource{ds})
|
||||
assert.EqualError(t, err, "update failed")
|
||||
}
|
||||
@@ -27,6 +27,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
|
||||
"github.com/grafana/grafana/pkg/services/promtypemigration"
|
||||
prov_alerting "github.com/grafana/grafana/pkg/services/provisioning/alerting"
|
||||
"github.com/grafana/grafana/pkg/services/provisioning/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/provisioning/datasources"
|
||||
@@ -59,6 +60,7 @@ func ProvideService(
|
||||
resourcePermissions accesscontrol.ReceiverPermissionsService,
|
||||
tracer tracing.Tracer,
|
||||
dual dualwrite.Service,
|
||||
promTypeMigrationProvider promtypemigration.PromTypeMigrationProvider,
|
||||
) (*ProvisioningServiceImpl, error) {
|
||||
s := &ProvisioningServiceImpl{
|
||||
Cfg: cfg,
|
||||
@@ -85,6 +87,7 @@ func ProvideService(
|
||||
folderService: folderService,
|
||||
resourcePermissions: resourcePermissions,
|
||||
tracer: tracer,
|
||||
migratePrometheusType: promTypeMigrationProvider.Run,
|
||||
}
|
||||
|
||||
if err := s.setDashboardProvisioner(); err != nil {
|
||||
@@ -120,6 +123,7 @@ func newProvisioningServiceImpl(
|
||||
newDashboardProvisioner dashboards.DashboardProvisionerFactory,
|
||||
provisionDatasources func(context.Context, string, datasources.BaseDataSourceService, datasources.CorrelationsStore, org.Service) error,
|
||||
provisionPlugins func(context.Context, string, pluginstore.Store, pluginsettings.Service, org.Service) error,
|
||||
migratePrometheusType func(context.Context) error,
|
||||
searchService searchV2.SearchService,
|
||||
) (*ProvisioningServiceImpl, error) {
|
||||
s := &ProvisioningServiceImpl{
|
||||
@@ -129,6 +133,7 @@ func newProvisioningServiceImpl(
|
||||
provisionPlugins: provisionPlugins,
|
||||
Cfg: setting.NewCfg(),
|
||||
searchService: searchService,
|
||||
migratePrometheusType: migratePrometheusType,
|
||||
}
|
||||
|
||||
if err := s.setDashboardProvisioner(); err != nil {
|
||||
@@ -168,6 +173,7 @@ type ProvisioningServiceImpl struct {
|
||||
tracer tracing.Tracer
|
||||
dual dualwrite.Service
|
||||
onceInitProvisioners sync.Once
|
||||
migratePrometheusType func(context.Context) error
|
||||
}
|
||||
|
||||
func (ps *ProvisioningServiceImpl) RunInitProvisioners(ctx context.Context) error {
|
||||
@@ -201,6 +207,15 @@ func (ps *ProvisioningServiceImpl) Run(ctx context.Context) error {
|
||||
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 {
|
||||
|
||||
@@ -170,6 +170,9 @@ func setup(t *testing.T) *serviceTestStruct {
|
||||
func(context.Context, string, pluginstore.Store, pluginsettings.Service, org.Service) error {
|
||||
return nil
|
||||
},
|
||||
func(context.Context) error {
|
||||
return nil
|
||||
},
|
||||
searchStub,
|
||||
)
|
||||
service.provisionAlerting = func(context.Context, prov_alerting.ProvisionerConfig) error {
|
||||
|
||||
Reference in New Issue
Block a user