Unified: Run resource data migrations at startup (#114857)

* chore: uncomment unified migration

* chore: adapt and fix tests

* chore: dynamically bump max conns if needed during migration

* chore: copilot suggestions

* chore: pass ctx in RegisterMigration

* chore: make playlists opt-out and dashboards opt-in

* chore: adjust dashboard test

* chore: disable enable log in test

* chore: address review comments

- do not use pointer config
- add migration registry

* chore: more consistent naming

* chore: fix playlist discovery test
This commit is contained in:
Rafael Bortolon Paulovic
2025-12-17 10:09:57 +01:00
committed by GitHub
parent 00ea4024a8
commit aa3b9dc4da
24 changed files with 705 additions and 274 deletions
@@ -1166,11 +1166,12 @@ func TestIntegrationConvertPrometheusEndpoints_Editor(t *testing.T) {
testinfra.SQLiteIntegrationTest(t)
dir, gpath := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
DisableLegacyAlerting: true,
EnableUnifiedAlerting: true,
DisableAnonymous: true,
AppModeProduction: true,
EnableRecordingRules: true,
DisableAuthZClientCache: true,
DisableLegacyAlerting: true,
EnableUnifiedAlerting: true,
DisableAnonymous: true,
AppModeProduction: true,
EnableRecordingRules: true,
})
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, gpath)
@@ -1192,10 +1192,11 @@ func TestIntegrationExportFileProvisionContactPoints(t *testing.T) {
func TestIntegrationFullpath(t *testing.T) {
dir, p := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
DisableLegacyAlerting: true,
EnableUnifiedAlerting: true,
DisableAnonymous: true,
AppModeProduction: true,
DisableAuthZClientCache: true,
DisableLegacyAlerting: true,
EnableUnifiedAlerting: true,
DisableAnonymous: true,
AppModeProduction: true,
})
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, p)
+15 -12
View File
@@ -53,10 +53,11 @@ func TestIntegrationAlertRulePermissions(t *testing.T) {
testinfra.SQLiteIntegrationTest(t)
dir, p := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
DisableLegacyAlerting: true,
EnableUnifiedAlerting: true,
DisableAnonymous: true,
AppModeProduction: true,
DisableAuthZClientCache: true,
DisableLegacyAlerting: true,
EnableUnifiedAlerting: true,
DisableAnonymous: true,
AppModeProduction: true,
})
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, p)
@@ -360,10 +361,11 @@ func TestIntegrationAlertRuleNestedPermissions(t *testing.T) {
testinfra.SQLiteIntegrationTest(t)
dir, p := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
DisableLegacyAlerting: true,
EnableUnifiedAlerting: true,
DisableAnonymous: true,
AppModeProduction: true,
DisableAuthZClientCache: true,
DisableLegacyAlerting: true,
EnableUnifiedAlerting: true,
DisableAnonymous: true,
AppModeProduction: true,
})
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, p)
@@ -1429,10 +1431,11 @@ func TestIntegrationRuleGroupSequence(t *testing.T) {
testinfra.SQLiteIntegrationTest(t)
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
DisableLegacyAlerting: true,
EnableUnifiedAlerting: true,
DisableAnonymous: true,
AppModeProduction: true,
DisableAuthZClientCache: true,
DisableLegacyAlerting: true,
EnableUnifiedAlerting: true,
DisableAnonymous: true,
AppModeProduction: true,
})
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, path)
@@ -32,8 +32,9 @@ func TestIntegrationAnnotations(t *testing.T) {
testutil.SkipIntegrationTestInShortMode(t)
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
DisableAnonymous: true,
EnableFeatureToggles: []string{featuremgmt.FlagAnnotationPermissionUpdate},
DisableAuthZClientCache: true,
DisableAnonymous: true,
EnableFeatureToggles: []string{featuremgmt.FlagAnnotationPermissionUpdate},
})
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, path)
noneUserID := tests.CreateUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
+64 -31
View File
@@ -12,6 +12,7 @@ import (
"testing"
"time"
"github.com/grafana/grafana/pkg/setting"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -38,8 +39,15 @@ func TestMain(m *testing.M) {
func TestIntegrationDashboardServiceValidation(t *testing.T) {
testutil.SkipIntegrationTestInShortMode(t)
unifiedConfig := make(map[string]setting.UnifiedStorageConfig)
for _, resource := range []string{"folders.folder.grafana.app", "dashboards.dashboard.grafana.app"} {
unifiedConfig[resource] = setting.UnifiedStorageConfig{
EnableMigration: true,
}
}
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
DisableAnonymous: true,
DisableAnonymous: true,
UnifiedStorageConfig: unifiedConfig,
})
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, path)
@@ -218,11 +226,12 @@ func TestIntegrationDashboardServiceValidation(t *testing.T) {
require.NoError(t, err)
})
t.Run("When updating uid with id", func(t *testing.T) {
dashboardWithDuplicatedLegacyAnnotation := "new-uid"
t.Run("When saving a dashboard with an already used legacy ID", func(t *testing.T) {
resp, err := postDashboard(t, grafanaListedAddr, "admin", "admin", map[string]interface{}{
"dashboard": map[string]interface{}{
"id": savedDashInFolder.ID, // nolint:staticcheck
"uid": "new-uid",
"uid": dashboardWithDuplicatedLegacyAnnotation,
"title": "Updated title",
},
"folderUid": savedDashInFolder.FolderUID,
@@ -234,7 +243,48 @@ func TestIntegrationDashboardServiceValidation(t *testing.T) {
require.NoError(t, err)
})
t.Run("When updating uid with a dashboard already using that uid", func(t *testing.T) {
t.Run("When updating a dashboard with legacy ID in multiple dashboards", func(t *testing.T) {
resp, err := postDashboard(t, grafanaListedAddr, "admin", "admin", map[string]interface{}{
"dashboard": map[string]interface{}{
"id": savedDashInFolder.ID, // nolint:staticcheck
"uid": savedDashInGeneralFolder.UID,
"title": "Updated title",
},
"folderUid": savedDashInFolder.FolderUID,
"overwrite": true,
})
require.NoError(t, err)
assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)
err = resp.Body.Close()
require.NoError(t, err)
// Delete the dashboard with duplicated legacy ID annotation
u := fmt.Sprintf("http://admin:admin@%s/api/dashboards/uid/%s", grafanaListedAddr, dashboardWithDuplicatedLegacyAnnotation)
req, err := http.NewRequest("DELETE", u, nil)
require.NoError(t, err)
resp, err = http.DefaultClient.Do(req)
require.NoError(t, err)
err = resp.Body.Close()
require.NoError(t, err)
assert.Equal(t, http.StatusOK, resp.StatusCode)
})
t.Run("When updating a dashboard already using that uid", func(t *testing.T) {
resp, err := postDashboard(t, grafanaListedAddr, "admin", "admin", map[string]interface{}{
"dashboard": map[string]interface{}{
"id": savedDashInFolder.ID,
"uid": savedDashInFolder.UID,
"title": "Dashboard with existing UID",
},
"folderUid": savedDashInFolder.FolderUID,
"overwrite": true,
})
require.NoError(t, err)
assert.Equal(t, http.StatusOK, resp.StatusCode)
err = resp.Body.Close()
require.NoError(t, err)
})
t.Run("When updating id with a dashboard already using that uid", func(t *testing.T) {
resp, err := postDashboard(t, grafanaListedAddr, "admin", "admin", map[string]interface{}{
"dashboard": map[string]interface{}{
"id": savedDashInFolder.ID, // nolint:staticcheck
@@ -268,8 +318,9 @@ func TestIntegrationDashboardServiceValidation(t *testing.T) {
require.NoError(t, err)
})
// Obs: in legacy, the dashboard request would fail
// After the dashboard is created, the user can see that there is an error with the library panel and can remove them manually
t.Run("When creating a dashboard that references a non-existent library panel", func(t *testing.T) {
originalCount := getDashboardCount(t, grafanaListedAddr, "admin", "admin")
resp, err := postDashboard(t, grafanaListedAddr, "admin", "admin", map[string]interface{}{
"dashboard": map[string]interface{}{
"title": "Bad dashboard",
@@ -285,15 +336,11 @@ func TestIntegrationDashboardServiceValidation(t *testing.T) {
},
})
require.NoError(t, err)
assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)
body, err := io.ReadAll(resp.Body)
assert.Equal(t, http.StatusOK, resp.StatusCode)
_, err = io.ReadAll(resp.Body)
require.NoError(t, err)
require.Contains(t, string(body), "library element could not be found")
err = resp.Body.Close()
require.NoError(t, err)
// A new dashboard is not created in this situation.
require.Equal(t, originalCount, getDashboardCount(t, grafanaListedAddr, "admin", "admin"))
})
}
@@ -332,7 +379,7 @@ func TestIntegrationDashboardQuota(t *testing.T) {
dashboardDTO := &plugindashboards.PluginDashboard{}
err = json.Unmarshal(b, dashboardDTO)
require.NoError(t, err)
require.EqualValues(t, 1, dashboardDTO.DashboardId)
require.EqualValues(t, "just testing", dashboardDTO.Title)
})
t.Run("when quota limit exceeds importing a dashboard should fail", func(t *testing.T) {
@@ -421,7 +468,7 @@ providers:
dashboardUID = d.UID
dashboardID = d.ID // nolint:staticcheck
}
assert.Equal(t, int64(1), dashboardID)
assert.Len(t, *dashboardList, 1)
testCases := []struct {
desc string
@@ -781,7 +828,7 @@ func TestIntegrationImportDashboardWithLibraryPanels(t *testing.T) {
},
{
"id": 2,
"title": "Library Panel 2",
"title": "Library Panel 2",
"type": "stat",
"gridPos": {"h": 8, "w": 12, "x": 12, "y": 0},
"libraryPanel": {
@@ -805,7 +852,7 @@ func TestIntegrationImportDashboardWithLibraryPanels(t *testing.T) {
}
},
"test-lib-panel-2": {
"uid": "test-lib-panel-2",
"uid": "test-lib-panel-2",
"name": "Test Library Panel 2",
"kind": 1,
"type": "stat",
@@ -1011,26 +1058,12 @@ func postDashboard(t *testing.T, grafanaListedAddr, user, password string, paylo
return http.Post(u, "application/json", bytes.NewBuffer(payloadBytes)) // nolint:gosec
}
func getDashboardCount(t *testing.T, grafanaListenAddr, user, password string) int {
endpoint := fmt.Sprintf("http://%s:%s@%s/apis/dashboard.grafana.app/v0alpha1/namespaces/default/search", user, password, grafanaListenAddr)
resp, err := http.Get(endpoint) //nolint:gosec
require.NoError(t, err)
body, err := io.ReadAll(resp.Body)
require.NoError(t, err)
require.NoError(t, resp.Body.Close())
var payload map[string]any
require.NoError(t, json.Unmarshal(body, &payload))
return int(payload["totalHits"].(float64))
}
func TestIntegrationDashboardServicePermissions(t *testing.T) {
testutil.SkipIntegrationTestInShortMode(t)
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
DisableAnonymous: true,
DisableAnonymous: true,
DisableAuthZClientCache: true,
})
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, path)
tests.CreateUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{