unified-storage: refactor Sql backend and sqlkv compat tests (#115849)

* move sql and sqlkv backends compatibility tests

* refactor compatibility tests

* run storage backend tests with and without rvmanager

* fix

* fix

* fmt

* fix

* address feedback

* fmt
This commit is contained in:
Will Assis
2026-01-08 15:06:44 -05:00
committed by GitHub
parent a79cda3328
commit f669bc4448
5 changed files with 319 additions and 296 deletions
@@ -11,7 +11,6 @@ import (
"testing"
"time"
"github.com/bwmarrin/snowflake"
"github.com/go-jose/go-jose/v4/jwt"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
@@ -106,37 +105,6 @@ func RunStorageBackendTest(t *testing.T, newBackend NewBackendFunc, opts *TestOp
}
}
func RunSQLStorageBackendCompatibilityTest(t *testing.T, newBackend NewBackendWithDBFunc, opts *TestOptions) {
if opts == nil {
opts = &TestOptions{}
}
if opts.NSPrefix == "" {
opts.NSPrefix = GenerateRandomNSPrefix()
}
t.Logf("Running tests with namespace prefix: %s", opts.NSPrefix)
cases := []struct {
name string
fn func(*testing.T, resource.StorageBackend, string, sqldb.DB)
}{
{TestKeyPathGeneration, runTestIntegrationBackendKeyPathGeneration},
}
for _, tc := range cases {
if shouldSkip := opts.SkipTests[tc.name]; shouldSkip {
t.Logf("Skipping test: %s", tc.name)
continue
}
t.Run(tc.name, func(t *testing.T) {
backend, db := newBackend(context.Background())
tc.fn(t, backend, opts.NSPrefix, db)
})
}
}
func runTestIntegrationBackendHappyPath(t *testing.T, backend resource.StorageBackend, nsPrefix string) {
ctx := types.WithAuthInfo(context.Background(), authn.NewAccessTokenAuthInfo(authn.Claims[authn.AccessTokenClaims]{
Claims: jwt.Claims{
@@ -1759,222 +1727,3 @@ func runTestIntegrationBackendOptimisticLocking(t *testing.T, backend resource.S
require.LessOrEqual(t, successes, 1, "at most one create should succeed (errors: %v)", errorMessages)
})
}
func runTestIntegrationBackendKeyPathGeneration(t *testing.T, backend resource.StorageBackend, nsPrefix string, db sqldb.DB) {
ctx := testutil.NewDefaultTestContext(t)
t.Run("Create resource", func(t *testing.T) {
// Create a test resource
key := &resourcepb.ResourceKey{
Group: "playlist.grafana.app",
Resource: "playlists",
Namespace: nsPrefix + "-default",
Name: "test-playlist-crud",
}
// Create the K8s unstructured object
testObj := &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "playlist.grafana.app/v0alpha1",
"kind": "Playlist",
"metadata": map[string]interface{}{
"name": "test-playlist-crud",
"namespace": nsPrefix + "-default",
"uid": "test-uid-crud-123",
},
"spec": map[string]interface{}{
"title": "My Test Playlist",
},
},
}
// Get metadata accessor
metaAccessor, err := utils.MetaAccessor(testObj)
require.NoError(t, err)
// Serialize to JSON
jsonBytes, err := testObj.MarshalJSON()
require.NoError(t, err)
// Create WriteEvent
writeEvent := resource.WriteEvent{
Type: resourcepb.WatchEvent_ADDED,
Key: key,
Value: jsonBytes,
Object: metaAccessor,
PreviousRV: 0, // Always 0 for new resources
GUID: "create-guid-crud-123",
}
// Create the resource using WriteEvent
createRV, err := backend.WriteEvent(ctx, writeEvent)
require.NoError(t, err)
require.Greater(t, createRV, int64(0))
// Verify created resource key_path
verifyKeyPath(t, db, ctx, key, "created", createRV, "")
t.Run("Update resource", func(t *testing.T) {
// Update the resource
testObj.Object["spec"] = map[string]interface{}{
"title": "My Updated Playlist",
}
updatedMetaAccessor, err := utils.MetaAccessor(testObj)
require.NoError(t, err)
updatedJsonBytes, err := testObj.MarshalJSON()
require.NoError(t, err)
updateEvent := resource.WriteEvent{
Type: resourcepb.WatchEvent_MODIFIED,
Key: key,
Value: updatedJsonBytes,
Object: updatedMetaAccessor,
PreviousRV: createRV,
GUID: fmt.Sprintf("update-guid-%d", createRV),
}
// Update the resource
updateRV, err := backend.WriteEvent(ctx, updateEvent)
require.NoError(t, err)
require.Greater(t, updateRV, createRV)
// Verify updated resource key_path
verifyKeyPath(t, db, ctx, key, "updated", updateRV, "")
t.Run("Delete resource", func(t *testing.T) {
deleteEvent := resource.WriteEvent{
Type: resourcepb.WatchEvent_DELETED,
Key: key,
Value: updatedJsonBytes, // Keep the last known value
Object: updatedMetaAccessor,
PreviousRV: updateRV,
GUID: fmt.Sprintf("delete-guid-%d", updateRV),
}
// Delete the resource
deleteRV, err := backend.WriteEvent(ctx, deleteEvent)
require.NoError(t, err)
require.Greater(t, deleteRV, updateRV)
// Verify deleted resource key_path
verifyKeyPath(t, db, ctx, key, "deleted", deleteRV, "")
})
})
})
t.Run("Resource with folder", func(t *testing.T) {
// Create a resource in a folder
folderKey := &resourcepb.ResourceKey{
Group: "dashboard.grafana.app",
Resource: "dashboards",
Namespace: nsPrefix + "-default",
Name: "my-dashboard",
}
// Create dashboard object with folder
dashboardObj := &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "dashboard.grafana.app/v0alpha1",
"kind": "Dashboard",
"metadata": map[string]interface{}{
"name": "my-dashboard",
"namespace": nsPrefix + "-default",
"uid": "dash-uid-456",
"annotations": map[string]interface{}{
"grafana.app/folder": "test-folder",
},
},
"spec": map[string]interface{}{
"title": "My Dashboard",
},
},
}
folderMetaAccessor, err := utils.MetaAccessor(dashboardObj)
require.NoError(t, err)
folderJsonBytes, err := dashboardObj.MarshalJSON()
require.NoError(t, err)
folderWriteEvent := resource.WriteEvent{
Type: resourcepb.WatchEvent_ADDED,
Key: folderKey,
Value: folderJsonBytes,
Object: folderMetaAccessor,
PreviousRV: 0,
GUID: "folder-guid-456",
}
// Create the dashboard in folder
folderRV, err := backend.WriteEvent(ctx, folderWriteEvent)
require.NoError(t, err)
require.Greater(t, folderRV, int64(0))
// Verify folder resource key_path includes folder
verifyKeyPath(t, db, ctx, folderKey, "created", folderRV, "test-folder")
})
}
// verifyKeyPath is a helper function to verify key_path generation
func verifyKeyPath(t *testing.T, db sqldb.DB, ctx context.Context, key *resourcepb.ResourceKey, action string, resourceVersion int64, expectedFolder string) {
var query string
if db.DriverName() == "postgres" {
query = "SELECT key_path, resource_version, action, folder FROM resource_history WHERE namespace = $1 AND name = $2 AND resource_version = $3"
} else {
query = "SELECT key_path, resource_version, action, folder FROM resource_history WHERE namespace = ? AND name = ? AND resource_version = ?"
}
rows, err := db.QueryContext(ctx, query, key.Namespace, key.Name, resourceVersion)
require.NoError(t, err)
require.True(t, rows.Next())
var keyPath string
var actualRV int64
var actualAction int
var actualFolder string
err = rows.Scan(&keyPath, &actualRV, &actualAction, &actualFolder)
require.NoError(t, err)
err = rows.Close()
require.NoError(t, err)
// Verify basic key_path format
require.Contains(t, keyPath, "unified/data/")
require.Contains(t, keyPath, key.Group)
require.Contains(t, keyPath, key.Resource)
require.Contains(t, keyPath, key.Namespace)
require.Contains(t, keyPath, key.Name)
// Verify action suffix
require.Contains(t, keyPath, fmt.Sprintf("~%s~", action))
// Verify snowflake calculation
expectedSnowflake := (((resourceVersion / 1000) - snowflake.Epoch) << (snowflake.NodeBits + snowflake.StepBits)) + (resourceVersion % 1000)
require.Contains(t, keyPath, fmt.Sprintf("/%d~", expectedSnowflake), fmt.Sprintf("actual RV: %d", actualRV))
// Verify folder if specified
if expectedFolder != "" {
require.Equal(t, expectedFolder, actualFolder)
require.Contains(t, keyPath, expectedFolder)
}
// Verify action code matches
var expectedActionCode int
switch action {
case "created":
expectedActionCode = 1
case "updated":
expectedActionCode = 2
case "deleted":
expectedActionCode = 3
}
require.Equal(t, expectedActionCode, actualAction)
t.Logf("Action: %s, RV: %d, Snowflake: %d", action, resourceVersion, expectedSnowflake)
t.Logf("Key_path: %s", keyPath)
if expectedFolder != "" {
t.Logf("Folder: %s", actualFolder)
}
}