From 7f34fae4392a7714d8c6aab31e5217274bff8ee6 Mon Sep 17 00:00:00 2001 From: Stephanie Hingtgen Date: Thu, 8 Jan 2026 14:51:42 -0700 Subject: [PATCH] Zanzana: Run dashboard integration tests backed by zanzana (#115771) --- .../integration/api_validation_test.go | 186 ++++++++++++++---- pkg/tests/apis/folder/folder_tree_test.go | 10 +- pkg/tests/apis/zanzana_reconcile.go | 13 +- pkg/tests/testinfra/testinfra.go | 46 ++++- 4 files changed, 200 insertions(+), 55 deletions(-) diff --git a/pkg/tests/apis/dashboard/integration/api_validation_test.go b/pkg/tests/apis/dashboard/integration/api_validation_test.go index ee40bf00b47..3bd8af61f6f 100644 --- a/pkg/tests/apis/dashboard/integration/api_validation_test.go +++ b/pkg/tests/apis/dashboard/integration/api_validation_test.go @@ -8,6 +8,7 @@ import ( "strconv" "strings" "testing" + "time" "github.com/stretchr/testify/require" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -132,6 +133,94 @@ func TestIntegrationDashboardAPIValidation(t *testing.T) { } } +func TestIntegrationDashboardAPIZanzana(t *testing.T) { + testutil.SkipIntegrationTestInShortMode(t) + + helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ + DisableDataMigrations: true, + AppModeProduction: true, + DisableAnonymous: true, + DisableAuthZClientCache: true, + DisableZanzanaCache: true, + DisableZanzanaServerCheckQueryCache: true, + ZanzanaReconciliationInterval: 1 * time.Second, + APIServerStorageType: "unified", + DBMaxConns: 10, + UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ + "dashboards.dashboard.grafana.app": { + DualWriterMode: rest.Mode5, + }, + "folders.folder.grafana.app": { + DualWriterMode: rest.Mode5, + }, + }, + EnableFeatureToggles: []string{ + "zanzana", + "zanzanaNoLegacyClient", + "kubernetesAuthzZanzanaSync", + }, + UnifiedStorageEnableSearch: true, + }) + + t.Cleanup(func() { + helper.Shutdown() + }) + + org1Ctx := createTestContext(t, helper, helper.Org1, rest.Mode5) + org2Ctx := createTestContext(t, helper, helper.OrgB, rest.Mode5) + + t.Run("Dashboard permission tests", func(t *testing.T) { + runDashboardPermissionTests(t, org1Ctx, true) + }) + + t.Run("Authorization tests for all identity types", func(t *testing.T) { + runAuthorizationTests(t, org1Ctx) + }) + t.Run("Dashboard HTTP API test", func(t *testing.T) { + runDashboardHttpTest(t, org1Ctx, org2Ctx) + }) + + t.Run("Cross-organization tests", func(t *testing.T) { + runCrossOrgTests(t, org1Ctx, org2Ctx) + }) +} + +// list tests will go very slowly if the cache is disabled - allow the cache solely for Lists +func TestIntegrationDashboardAPIZanzanaList(t *testing.T) { + testutil.SkipIntegrationTestInShortMode(t) + + helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ + DisableDataMigrations: true, + AppModeProduction: true, + DisableAnonymous: true, + APIServerStorageType: "unified", + DBMaxConns: 4, + UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ + "dashboards.dashboard.grafana.app": { + DualWriterMode: rest.Mode5, + }, + "folders.folder.grafana.app": { + DualWriterMode: rest.Mode5, + }, + }, + EnableFeatureToggles: []string{ + "zanzana", + "zanzanaNoLegacyClient", + "kubernetesAuthzZanzanaSync", + }, + UnifiedStorageEnableSearch: true, + ZanzanaReconciliationInterval: 100 * time.Millisecond, + }) + + t.Cleanup(func() { + helper.Shutdown() + }) + + org1Ctx := createTestContext(t, helper, helper.Org1, rest.Mode5) + + runDashboardListTests(t, org1Ctx) +} + // TestIntegrationDashboardAPI tests the dashboard K8s API func TestIntegrationDashboardAPI(t *testing.T) { testutil.SkipIntegrationTestInShortMode(t) @@ -211,11 +300,11 @@ func runDashboardValidationTests(t *testing.T, ctx TestContext) { t.Run("reject dashboard with existing UID", func(t *testing.T) { // Create a dashboard with a specific UID specificUID := "existing-uid-dash" - createdDash, err := createDashboard(t, adminClient, "Dashboard with Specific UID", nil, &specificUID) + createdDash, err := createDashboard(t, adminClient, "Dashboard with Specific UID", nil, &specificUID, ctx.Helper) require.NoError(t, err) // Try to create another dashboard with the same UID - _, err = createDashboard(t, adminClient, "Another Dashboard with Same UID", nil, &specificUID) + _, err = createDashboard(t, adminClient, "Another Dashboard with Same UID", nil, &specificUID, ctx.Helper) require.Error(t, err) // Clean up @@ -227,14 +316,14 @@ func runDashboardValidationTests(t *testing.T, ctx TestContext) { t.Run("reject dashboard with too long UID", func(t *testing.T) { // Create a dashboard with a long UID (over 40 chars) longUID := "this-uid-is-way-too-long-for-a-dashboard-uid-12345678901234567890" - _, err := createDashboard(t, adminClient, "Dashboard with Long UID", nil, &longUID) + _, err := createDashboard(t, adminClient, "Dashboard with Long UID", nil, &longUID, ctx.Helper) require.Error(t, err) }) // Test creating dashboard with invalid UID characters t.Run("reject dashboard with invalid UID characters", func(t *testing.T) { invalidUID := "invalid/uid/with/slashes" - _, err := createDashboard(t, adminClient, "Dashboard with Invalid UID", nil, &invalidUID) + _, err := createDashboard(t, adminClient, "Dashboard with Invalid UID", nil, &invalidUID, ctx.Helper) require.Error(t, err) }) }) @@ -243,21 +332,21 @@ func runDashboardValidationTests(t *testing.T, ctx TestContext) { t.Run("Dashboard title validations", func(t *testing.T) { // Test empty title t.Run("reject dashboard with empty title", func(t *testing.T) { - _, err := createDashboard(t, adminClient, "", nil, nil) + _, err := createDashboard(t, adminClient, "", nil, nil, ctx.Helper) require.Error(t, err) }) // Test long title t.Run("reject dashboard with excessively long title", func(t *testing.T) { veryLongTitle := strings.Repeat("a", 10000) - _, err := createDashboard(t, adminClient, veryLongTitle, nil, nil) + _, err := createDashboard(t, adminClient, veryLongTitle, nil, nil, ctx.Helper) require.Error(t, err) }) // Test updating dashboard with empty title t.Run("reject dashboard update with empty title", func(t *testing.T) { // First create a valid dashboard - dash, err := createDashboard(t, adminClient, "Valid Dashboard Title", nil, nil) + dash, err := createDashboard(t, adminClient, "Valid Dashboard Title", nil, nil, ctx.Helper) require.NoError(t, err) require.NotNil(t, dash) @@ -273,7 +362,7 @@ func runDashboardValidationTests(t *testing.T, ctx TestContext) { // Test updating dashboard with excessively long title t.Run("reject dashboard update with excessively long title", func(t *testing.T) { // First create a valid dashboard - dash, err := createDashboard(t, adminClient, "Valid Dashboard Title", nil, nil) + dash, err := createDashboard(t, adminClient, "Valid Dashboard Title", nil, nil, ctx.Helper) require.NoError(t, err) require.NotNil(t, dash) @@ -291,7 +380,7 @@ func runDashboardValidationTests(t *testing.T, ctx TestContext) { t.Run("Dashboard message validations", func(t *testing.T) { // Test long message t.Run("reject dashboard with excessively long update message", func(t *testing.T) { - dash, err := createDashboard(t, adminClient, "Regular dashboard", nil, nil) + dash, err := createDashboard(t, adminClient, "Regular dashboard", nil, nil, ctx.Helper) require.NoError(t, err) veryLongMessage := strings.Repeat("a", 600) @@ -308,14 +397,14 @@ func runDashboardValidationTests(t *testing.T, ctx TestContext) { // Test non-existent folder UID t.Run("reject dashboard with non-existent folder UID", func(t *testing.T) { nonExistentFolderUID := "non-existent-folder-uid" - _, err := createDashboard(t, adminClient, "Dashboard in Non-existent Folder", &nonExistentFolderUID, nil) + _, err := createDashboard(t, adminClient, "Dashboard in Non-existent Folder", &nonExistentFolderUID, nil, ctx.Helper) ctx.Helper.EnsureStatusError(err, http.StatusNotFound, "folders.folder.grafana.app \"non-existent-folder-uid\" not found") }) t.Run("allow moving folder to general folder", func(t *testing.T) { folder1 := createFolderObject(t, "folder1", "default", "") folder1UID := folder1.GetName() - dash, err := createDashboard(t, adminClient, "Dashboard in a Folder", &folder1UID, nil) + dash, err := createDashboard(t, adminClient, "Dashboard in a Folder", &folder1UID, nil, ctx.Helper) require.NoError(t, err) generalFolderUID := "" @@ -437,7 +526,7 @@ func runDashboardValidationTests(t *testing.T, ctx TestContext) { // Test version increment on update t.Run("version increments on dashboard update", func(t *testing.T) { // Create a dashboard with admin - dash, err := createDashboard(t, adminClient, "Dashboard for Version Test", nil, nil) + dash, err := createDashboard(t, adminClient, "Dashboard for Version Test", nil, nil, ctx.Helper) require.NoError(t, err, "Failed to create dashboard for version test") dashUID := dash.GetName() @@ -464,7 +553,7 @@ func runDashboardValidationTests(t *testing.T, ctx TestContext) { // Test generation conflict when updating concurrently t.Run("reject update with version conflict", func(t *testing.T) { // Create a dashboard with admin - dash, err := createDashboard(t, adminClient, "Dashboard for Version Conflict Test", nil, nil) + dash, err := createDashboard(t, adminClient, "Dashboard for Version Conflict Test", nil, nil, ctx.Helper) require.NoError(t, err, "Failed to create dashboard for version conflict test") dashUID := dash.GetName() @@ -517,7 +606,7 @@ func runDashboardValidationTests(t *testing.T, ctx TestContext) { t.Run("dashboard version history available, even for UIDs ending in hyphen", func(t *testing.T) { dashboardUID := "test-dashboard-" - dash, err := createDashboard(t, adminClient, "Dashboard with uid ending in hyphen", nil, &dashboardUID) + dash, err := createDashboard(t, adminClient, "Dashboard with uid ending in hyphen", nil, &dashboardUID, ctx.Helper) require.NoError(t, err) updatedDash, err := updateDashboard(t, adminClient, dash, "Updated dashboard with uid ending in hyphen", nil) @@ -564,7 +653,7 @@ func runDashboardValidationTests(t *testing.T, ctx TestContext) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { // Create a dashboard with admin - dash, err := createDashboard(t, adminClient, "Dashboard for Provisioning Test", nil, nil) + dash, err := createDashboard(t, adminClient, "Dashboard for Provisioning Test", nil, nil, ctx.Helper) require.NoError(t, err, "Failed to create dashboard for provisioning test") dashUID := dash.GetName() @@ -689,7 +778,7 @@ func runDashboardValidationTests(t *testing.T, ctx TestContext) { // Create a dashboard with a specific UID to make it easier to manage specificUID := "size-limit-test-dash" - dash, err := createDashboard(t, adminClient, "Dashboard Exceeding Size Limit", nil, &specificUID) + dash, err := createDashboard(t, adminClient, "Dashboard Exceeding Size Limit", nil, &specificUID, ctx.Helper) require.NoError(t, err) meta, _ := utils.MetaAccessor(dash) @@ -877,11 +966,11 @@ func runQuotaTests(t *testing.T, ctx TestContext) { require.NoError(t, err, "Failed to update quota") // Create first dashboard - should succeed - dash1, err := createDashboard(t, adminClient, fmt.Sprintf("Quota Test Dashboard 1 (%s)", tc.name), nil, nil) + dash1, err := createDashboard(t, adminClient, fmt.Sprintf("Quota Test Dashboard 1 (%s)", tc.name), nil, nil, ctx.Helper) require.NoError(t, err, "Failed to create first dashboard") // Create second dashboard - should fail due to quota - _, err = createDashboard(t, adminClient, fmt.Sprintf("Quota Test Dashboard 2 (%s)", tc.name), nil, nil) + _, err = createDashboard(t, adminClient, fmt.Sprintf("Quota Test Dashboard 2 (%s)", tc.name), nil, nil, ctx.Helper) require.Error(t, err, "Creating second dashboard should fail due to quota") require.Contains(t, err.Error(), "quota", "Error should mention quota") @@ -911,6 +1000,8 @@ func runQuotaTests(t *testing.T, ctx TestContext) { // Helper function to create test context for an organization func createTestContext(t *testing.T, helper *apis.K8sTestHelper, orgUsers apis.OrgUsers, dualWriterMode rest.DualWriterMode) TestContext { + apis.AwaitZanzanaReconcileNext(t, helper) + // Create test folder folderTitle := "Test Folder Org " + strconv.FormatInt(orgUsers.Admin.Identity.GetOrgID(), 10) testFolder, err := createFolder(t, helper, orgUsers.Admin, folderTitle) @@ -1013,6 +1104,8 @@ func createFolder(t *testing.T, helper *apis.K8sTestHelper, user apis.User, titl return nil, err } + apis.AwaitZanzanaReconcileNext(t, helper) + meta, _ := utils.MetaAccessor(createdFolder) // Create a folder struct to return (for compatibility with existing code) @@ -1087,7 +1180,7 @@ func markDashboardObjectAsProvisioned(t *testing.T, dashboard *unstructured.Unst } // Create a dashboard -func createDashboard(t *testing.T, client *apis.K8sResourceClient, title string, folderUID *string, uid *string) (*unstructured.Unstructured, error) { +func createDashboard(t *testing.T, client *apis.K8sResourceClient, title string, folderUID *string, uid *string, helper *apis.K8sTestHelper) (*unstructured.Unstructured, error) { t.Helper() var folderUIDStr string @@ -1111,6 +1204,8 @@ func createDashboard(t *testing.T, client *apis.K8sResourceClient, title string, return nil, err } + apis.AwaitZanzanaReconcileNext(t, helper) + // Fetch the generated object to ensure we're not running into any caching or UID mismatch issues databaseDash, err := client.Resource.Get(context.Background(), createdDash.GetName(), v1.GetOptions{}) if err != nil { @@ -1254,11 +1349,13 @@ func runAuthorizationTests(t *testing.T, ctx TestContext) { {name: "in folder", folderUID: ctx.TestFolder.UID}, } + apis.AwaitZanzanaReconcileNext(t, ctx.Helper) + for _, loc := range locations { t.Run(loc.name, func(t *testing.T) { if roleCapabilities.canCreate { // Test can create dashboard - dash, err := createDashboard(t, identity.DashboardClient, identity.Name+" Dashboard "+loc.name, &loc.folderUID, nil) + dash, err := createDashboard(t, identity.DashboardClient, identity.Name+" Dashboard "+loc.name, &loc.folderUID, nil, ctx.Helper) require.NoError(t, err) require.NotNil(t, dash) @@ -1274,7 +1371,7 @@ func runAuthorizationTests(t *testing.T, ctx TestContext) { require.NoError(t, err) } else { // Test cannot create dashboard - _, err := createDashboard(t, identity.DashboardClient, identity.Name+" Dashboard "+loc.name, nil, nil) + _, err := createDashboard(t, identity.DashboardClient, identity.Name+" Dashboard "+loc.name, nil, nil, ctx.Helper) require.Error(t, err) } }) @@ -1284,7 +1381,7 @@ func runAuthorizationTests(t *testing.T, ctx TestContext) { // Test dashboard updates t.Run("dashboard update", func(t *testing.T) { // Create a dashboard with admin - dash, err := createDashboard(t, adminClient, "Dashboard to Update by "+identity.Name, nil, nil) + dash, err := createDashboard(t, adminClient, "Dashboard to Update by "+identity.Name, nil, nil, ctx.Helper) require.NoError(t, err) require.NotNil(t, dash) @@ -1311,7 +1408,7 @@ func runAuthorizationTests(t *testing.T, ctx TestContext) { // Test dashboard deletion permissions t.Run("dashboard deletion", func(t *testing.T) { // Create a dashboard with admin - dash, err := createDashboard(t, adminClient, "Dashboard for deletion test by "+identity.Name, nil, nil) + dash, err := createDashboard(t, adminClient, "Dashboard for deletion test by "+identity.Name, nil, nil, ctx.Helper) require.NoError(t, err) require.NotNil(t, dash) @@ -1331,7 +1428,7 @@ func runAuthorizationTests(t *testing.T, ctx TestContext) { // Test dashboard viewing for all roles t.Run("dashboard viewing", func(t *testing.T) { // Create a dashboard with admin - dash, err := createDashboard(t, adminClient, "Dashboard for "+identity.Name+" to view", nil, nil) + dash, err := createDashboard(t, adminClient, "Dashboard for "+identity.Name+" to view", nil, nil, ctx.Helper) require.NoError(t, err) require.NotNil(t, dash) @@ -1363,7 +1460,7 @@ func runDashboardPermissionTests(t *testing.T, ctx TestContext, kubernetesDashbo // Test custom dashboard permissions t.Run("Dashboard with custom permissions", func(t *testing.T) { // Create a dashboard with admin - dash, err := createDashboard(t, adminClient, "Dashboard with Custom Permissions", nil, nil) + dash, err := createDashboard(t, adminClient, "Dashboard with Custom Permissions", nil, nil, ctx.Helper) require.NoError(t, err) require.NotNil(t, dash) @@ -1394,12 +1491,12 @@ func runDashboardPermissionTests(t *testing.T, ctx TestContext, kubernetesDashbo // Test dashboard-specific permission overrides (new test case) t.Run("Dashboard-specific permission overrides", func(t *testing.T) { // Create multiple dashboards with admin - dash1, err := createDashboard(t, adminClient, "Dashboard with No Custom Permissions", nil, nil) + dash1, err := createDashboard(t, adminClient, "Dashboard with No Custom Permissions", nil, nil, ctx.Helper) require.NoError(t, err) require.NotNil(t, dash1) dash1UID := dash1.GetName() - dash2, err := createDashboard(t, adminClient, "Dashboard with Viewer Edit Permission", nil, nil) + dash2, err := createDashboard(t, adminClient, "Dashboard with Viewer Edit Permission", nil, nil, ctx.Helper) require.NoError(t, err) require.NotNil(t, dash2) dash2UID := dash2.GetName() @@ -1443,7 +1540,7 @@ func runDashboardPermissionTests(t *testing.T, ctx TestContext, kubernetesDashbo setResourceUserPermission(t, ctx, ctx.AdminUser, false, folderUID, addUserPermission(t, nil, ctx.ViewerUser, ResourcePermissionLevelEdit)) // Create a dashboard in the folder with admin - dash, err := createDashboard(t, adminClient, "Dashboard in Custom Permission Folder", &folderUID, nil) + dash, err := createDashboard(t, adminClient, "Dashboard in Custom Permission Folder", &folderUID, nil, ctx.Helper) require.NoError(t, err) require.NotNil(t, dash) @@ -1462,7 +1559,7 @@ func runDashboardPermissionTests(t *testing.T, ctx TestContext, kubernetesDashbo require.Equal(t, "Updated by Viewer with Folder Permission", meta.FindTitle("")) // User should be able to create a dashboard in the folder - dashViewer, err := createDashboard(t, viewerClient, "Dashboard created by Viewer in Custom Permission Folder", &folderUID, nil) + dashViewer, err := createDashboard(t, viewerClient, "Dashboard created by Viewer in Custom Permission Folder", &folderUID, nil, ctx.Helper) require.NoError(t, err) require.NotNil(t, dashViewer) @@ -1509,7 +1606,7 @@ func runDashboardPermissionTests(t *testing.T, ctx TestContext, kubernetesDashbo setResourceUserPermission(t, ctx, ctx.AdminUser, false, folder2UID, addUserPermission(t, nil, ctx.ViewerUser, ResourcePermissionLevelEdit)) // Have the viewer create a dashboard in folder2 - viewerDash, err := createDashboard(t, viewerClient, "Dashboard created by Viewer in Edit Permission Folder", &folder2UID, nil) + viewerDash, err := createDashboard(t, viewerClient, "Dashboard created by Viewer in Edit Permission Folder", &folder2UID, nil, ctx.Helper) require.NoError(t, err, "Viewer should be able to create dashboard in folder with edit permissions") require.NotNil(t, viewerDash) dashUID := viewerDash.GetName() @@ -1544,7 +1641,7 @@ func runDashboardPermissionTests(t *testing.T, ctx TestContext, kubernetesDashbo // Test creator permissions (new test case) t.Run("Creator of dashboard gets admin permission", func(t *testing.T) { // Create a dashboard as an editor user (not admin) - editorCreatedDash, err := createDashboard(t, editorClient, "Dashboard Created by Editor", nil, nil) + editorCreatedDash, err := createDashboard(t, editorClient, "Dashboard Created by Editor", nil, nil, ctx.Helper) require.NoError(t, err) require.NotNil(t, editorCreatedDash) dashUID := editorCreatedDash.GetName() @@ -1575,7 +1672,7 @@ func runDashboardPermissionTests(t *testing.T, ctx TestContext, kubernetesDashbo t.Run("Admin can override creator permissions", func(t *testing.T) { t.Skip("Have to double check if that's actually the case") // Create a dashboard as an editor user (not admin) - editorCreatedDash, err := createDashboard(t, editorClient, "Dashboard Created by Editor for Permission Test", nil, nil) + editorCreatedDash, err := createDashboard(t, editorClient, "Dashboard Created by Editor for Permission Test", nil, nil, ctx.Helper) require.NoError(t, err) require.NotNil(t, editorCreatedDash) dashUID := editorCreatedDash.GetName() @@ -1614,7 +1711,7 @@ func runDashboardPermissionTests(t *testing.T, ctx TestContext, kubernetesDashbo otherOrgClient := getResourceClient(t, ctx.Helper, ctx.Helper.OrgB.Viewer, getDashboardGVR()) // Create a dashboard with admin in the current org - dash, err := createDashboard(t, adminClient, "Dashboard for Cross-Org Permissions Test", nil, nil) + dash, err := createDashboard(t, adminClient, "Dashboard for Cross-Org Permissions Test", nil, nil, ctx.Helper) require.NoError(t, err) require.NotNil(t, dash) org1DashUID := dash.GetName() @@ -1703,11 +1800,11 @@ func runCrossOrgTests(t *testing.T, org1Ctx, org2Ctx TestContext) { dashTitle := "Cross-Org Dashboard" // Create in org1 - dash1, err := createDashboard(t, org1SuperAdminClient, dashTitle, nil, &uid) + dash1, err := createDashboard(t, org1SuperAdminClient, dashTitle, nil, &uid, org1Ctx.Helper) require.NoError(t, err, "Failed to create dashboard in org1") // Create in org2 with same UID - should succeed (UIDs only need to be unique within an org) - dash2, err := createDashboard(t, org2SuperAdminClient, dashTitle, nil, &uid) + dash2, err := createDashboard(t, org2SuperAdminClient, dashTitle, nil, &uid, org2Ctx.Helper) require.NoError(t, err, "Failed to create dashboard with same UID in org2") // Verify both dashboards were created @@ -1793,12 +1890,12 @@ func runCrossOrgTests(t *testing.T, org1Ctx, org2Ctx TestContext) { // Test cross-organization access t.Run("Cross-organization access", func(t *testing.T) { // Create dashboards in both orgs - org1Dashboard, err := createDashboard(t, org1SuperAdminClient, "Org1 Dashboard", nil, nil) + org1Dashboard, err := createDashboard(t, org1SuperAdminClient, "Org1 Dashboard", nil, nil, org1Ctx.Helper) require.NoError(t, err) require.NotNil(t, org1Dashboard) org1DashUID := org1Dashboard.GetName() - org2Dashboard, err := createDashboard(t, org2SuperAdminClient, "Org2 Dashboard", nil, nil) + org2Dashboard, err := createDashboard(t, org2SuperAdminClient, "Org2 Dashboard", nil, nil, org2Ctx.Helper) require.NoError(t, err) require.NotNil(t, org2Dashboard) org2DashUID := org2Dashboard.GetName() @@ -1957,6 +2054,8 @@ func setResourceUserPermission(t *testing.T, ctx TestContext, actingUser apis.Us // Check response status code require.Equal(t, http.StatusOK, resp.Response.StatusCode, "Failed to set permissions for %s", resourceUID) + + apis.AwaitZanzanaReconcileNext(t, ctx.Helper) } // Test creating a dashboard via HTTP and deleting it @@ -2033,6 +2132,7 @@ func runDashboardHttpTest(t *testing.T, ctx TestContext, foreignOrgCtx TestConte for _, userTC := range userTestCases { testName := fmt.Sprintf("%s by %s", locTC.name, userTC.name) t.Run(testName, func(t *testing.T) { + apis.AwaitZanzanaReconcileNext(t, ctx.Helper) // Create a unique dashboard UID - ensure it's 40 chars max dashboardUID := fmt.Sprintf("test-%s-%s-%s", "POST", @@ -2078,6 +2178,8 @@ func runDashboardHttpTest(t *testing.T, ctx TestContext, foreignOrgCtx TestConte ContentType: "application/json", }, &struct{}{}) + apis.AwaitZanzanaReconcileNext(t, ctx.Helper) + // Check if the creation was successful or failed as expected adminClient := getResourceClient(t, ctx.Helper, ctx.AdminUser, getDashboardGVR()) @@ -2421,7 +2523,7 @@ func runDashboardListTests(t *testing.T, ctx TestContext) { // Create all test resources (folders, dashboards) in one loop for i, fc := range folderConfigs { // Create root dashboard - rootDash, err := createDashboard(t, adminClient, fmt.Sprintf("Root Dashboard - %s", fc.name), nil, nil) + rootDash, err := createDashboard(t, adminClient, fmt.Sprintf("Root Dashboard - %s", fc.name), nil, nil, ctx.Helper) require.NoError(t, err) rootDashboards[i] = rootDash fc.permissions(t, ctx, rootDash.GetName(), true) @@ -2433,7 +2535,7 @@ func runDashboardListTests(t *testing.T, ctx TestContext) { fc.permissions(t, ctx, folder.UID, false) // Create dashboard in folder - folderDash, err := createDashboard(t, adminClient, fmt.Sprintf("Dashboard in %s folder", fc.name), &folder.UID, nil) + folderDash, err := createDashboard(t, adminClient, fmt.Sprintf("Dashboard in %s folder", fc.name), &folder.UID, nil, ctx.Helper) require.NoError(t, err) folderDashboards[i] = folderDash } @@ -2594,10 +2696,10 @@ func runDashboardTrashTests(t *testing.T, ctx TestContext) { t.Run("regular dashboards appear in trash but provisioned ones do not", func(t *testing.T) { // create two dashboards, one that is provisioned and one that is not - regularDash, err := createDashboard(t, adminClient, "Regular Dashboard for Trash Comparison", nil, nil) + regularDash, err := createDashboard(t, adminClient, "Regular Dashboard for Trash Comparison", nil, nil, ctx.Helper) require.NoError(t, err) regularDashUID := regularDash.GetName() - provisionedDash, err := createDashboard(t, adminClient, "Provisioned Dashboard for Trash Comparison", nil, nil) + provisionedDash, err := createDashboard(t, adminClient, "Provisioned Dashboard for Trash Comparison", nil, nil, ctx.Helper) require.NoError(t, err) provisionedDashUID := provisionedDash.GetName() meta, err := utils.MetaAccessor(provisionedDash) @@ -2626,7 +2728,7 @@ func runDashboardTrashTests(t *testing.T, ctx TestContext) { }) t.Run("permission checks - admin can see everything, users can see their own deleted items", func(t *testing.T) { - dash, err := createDashboard(t, editorClient, "Dashboard for Trash Test", nil, nil) + dash, err := createDashboard(t, editorClient, "Dashboard for Trash Test", nil, nil, ctx.Helper) require.NoError(t, err) dashUID := dash.GetName() err = editorClient.Resource.Delete(context.Background(), dashUID, v1.DeleteOptions{}) diff --git a/pkg/tests/apis/folder/folder_tree_test.go b/pkg/tests/apis/folder/folder_tree_test.go index 613d021b236..227dec47d73 100644 --- a/pkg/tests/apis/folder/folder_tree_test.go +++ b/pkg/tests/apis/folder/folder_tree_test.go @@ -36,10 +36,12 @@ func TestIntegrationFolderTreeZanzana(t *testing.T) { testutil.SkipIntegrationTestInShortMode(t) runIntegrationFolderTree(t, testinfra.GrafanaOpts{ - DisableDataMigrations: true, - AppModeProduction: true, - DisableAnonymous: true, - APIServerStorageType: "unified", + DisableDataMigrations: true, + AppModeProduction: true, + DisableAnonymous: true, + DisableAuthZClientCache: true, + DisableZanzanaServerCheckQueryCache: true, + APIServerStorageType: "unified", UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ "dashboards.dashboard.grafana.app": { DualWriterMode: grafanarest.Mode5, diff --git a/pkg/tests/apis/zanzana_reconcile.go b/pkg/tests/apis/zanzana_reconcile.go index f8a5673fed7..d63d46491fe 100644 --- a/pkg/tests/apis/zanzana_reconcile.go +++ b/pkg/tests/apis/zanzana_reconcile.go @@ -18,7 +18,10 @@ import ( const zanzanaReconcileLastSuccessMetric = "grafana_zanzana_reconcile_last_success_timestamp_seconds" -// AwaitZanzanaReconcileNext waits for the next Zanzana reconciliation cycle to complete. +// AwaitZanzanaReconcileNext waits for a Zanzana reconciliation cycle whose last-success timestamp +// has been incremented from its current value. This ensures a reconciliation has occurred after +// this function is called. +// // It is a no-op unless the `zanzana` feature toggle is enabled for the running test env. func AwaitZanzanaReconcileNext(t *testing.T, helper *K8sTestHelper) { t.Helper() @@ -31,18 +34,14 @@ func AwaitZanzanaReconcileNext(t *testing.T, helper *K8sTestHelper) { return } - prev, ok := getZanzanaReconcileLastSuccessTimestampSeconds(t, helper) - if !ok { - prev = 0 - } - + baselineTimestamp, _ := getZanzanaReconcileLastSuccessTimestampSeconds(t, helper) require.EventuallyWithT(t, func(c *assert.CollectT) { ts, ok := getZanzanaReconcileLastSuccessTimestampSeconds(t, helper) assert.True(c, ok, "expected to find %s in /metrics", zanzanaReconcileLastSuccessMetric) if !ok { return } - assert.Greater(c, ts, prev, "expected %s (%v) > %v", zanzanaReconcileLastSuccessMetric, ts, prev) + assert.Greater(c, ts, baselineTimestamp, "expected %s (%v) > baseline (%v)", zanzanaReconcileLastSuccessMetric, ts, baselineTimestamp) }, 30*time.Second, 50*time.Millisecond) } diff --git a/pkg/tests/testinfra/testinfra.go b/pkg/tests/testinfra/testinfra.go index 17f1e9d84b2..88f65223675 100644 --- a/pkg/tests/testinfra/testinfra.go +++ b/pkg/tests/testinfra/testinfra.go @@ -370,6 +370,39 @@ func CreateGrafDir(t *testing.T, opts GrafanaOpts) (string, string) { require.NoError(t, err) } + if opts.DisableZanzanaServerCheckQueryCache { + zanzanaServerSect, err := cfg.NewSection("zanzana.server") + require.NoError(t, err) + _, err = zanzanaServerSect.NewKey("check_cache_limit", "0") + require.NoError(t, err) + _, err = zanzanaServerSect.NewKey("cache_controller_enabled", "false") + require.NoError(t, err) + _, err = zanzanaServerSect.NewKey("cache_controller_ttl", "0") + require.NoError(t, err) + _, err = zanzanaServerSect.NewKey("check_query_cache_enabled", "false") + require.NoError(t, err) + _, err = zanzanaServerSect.NewKey("check_query_cache_ttl", "0") + require.NoError(t, err) + _, err = zanzanaServerSect.NewKey("check_iterator_cache_enabled", "false") + require.NoError(t, err) + _, err = zanzanaServerSect.NewKey("check_iterator_cache_max_results", "0") + require.NoError(t, err) + _, err = zanzanaServerSect.NewKey("check_iterator_cache_ttl", "0") + require.NoError(t, err) + _, err = zanzanaServerSect.NewKey("list_objects_iterator_cache_enabled", "false") + require.NoError(t, err) + _, err = zanzanaServerSect.NewKey("list_objects_iterator_cache_max_results", "0") + require.NoError(t, err) + _, err = zanzanaServerSect.NewKey("list_objects_iterator_cache_ttl", "0") + require.NoError(t, err) + _, err = zanzanaServerSect.NewKey("shared_iterator_enabled", "false") + require.NoError(t, err) + _, err = zanzanaServerSect.NewKey("shared_iterator_limit", "0") + require.NoError(t, err) + _, err = zanzanaServerSect.NewKey("shared_iterator_ttl", "0") + require.NoError(t, err) + } + analyticsSect, err := cfg.NewSection("analytics") require.NoError(t, err) _, err = analyticsSect.NewKey("intercom_secret", "intercom_secret_at_config") @@ -641,9 +674,14 @@ func CreateGrafDir(t *testing.T, opts GrafanaOpts) (string, string) { require.NoError(t, err) _, err = dbSection.NewKey("query_retries", fmt.Sprintf("%d", queryRetries)) require.NoError(t, err) - _, err = dbSection.NewKey("max_open_conn", "2") + maxConns := opts.DBMaxConns + if maxConns <= 0 { + maxConns = 2 + } + + _, err = dbSection.NewKey("max_open_conn", fmt.Sprintf("%d", maxConns)) require.NoError(t, err) - _, err = dbSection.NewKey("max_idle_conn", "2") + _, err = dbSection.NewKey("max_idle_conn", fmt.Sprintf("%d", maxConns)) require.NoError(t, err) cfgPath := filepath.Join(cfgDir, "test.ini") @@ -706,6 +744,10 @@ type GrafanaOpts struct { DisableAuthZClientCache bool ZanzanaReconciliationInterval time.Duration DisableZanzanaCache bool + DisableZanzanaServerCheckQueryCache bool + + // If set to 0, the default (2) is used. + DBMaxConns int // Allow creating grafana dir beforehand Dir string