Unified Storage: Adds pruner to kv backend (#110549)

* WIP adding pruner to kv store impl

* pruner only keeps 20 most recent versions

* ignore grafana-kv-data folder

* extracts some stuff to pruner.go file. Adds tests. Adds kvBackendOptions.

* update logging, comments, exports kvbackendoptions fields

* update nooppruner ref

* fixes field casing in test

* fix test

* linter fixes

* remove comment

* make KvStorageBackend private

* Adds pruner key validation and tests. Fixes broken tests.

* update error message when validating pruner key
This commit is contained in:
owensmallwood
2025-09-05 10:02:11 -06:00
committed by GitHub
parent f0095d84e3
commit d715bda8af
7 changed files with 386 additions and 69 deletions
@@ -27,7 +27,14 @@ var appsNamespace = NamespacedResource{
func setupTestStorageBackend(t *testing.T) *kvStorageBackend {
kv := setupTestKV(t)
return NewKvStorageBackend(kv)
opts := KvBackendOptions{
KvStore: kv,
WithPruner: true,
}
backend, err := NewKvStorageBackend(opts)
kvBackend := backend.(*kvStorageBackend)
require.NoError(t, err)
return kvBackend
}
func TestNewKvStorageBackend(t *testing.T) {
@@ -1180,6 +1187,141 @@ func TestKvStorageBackend_GetResourceStats_Success(t *testing.T) {
require.Equal(t, int64(2), filteredStats[0].Count)
}
func TestKvStorageBackend_PruneEvents(t *testing.T) {
t.Run("will prune oldest events when exceeding limit", func(t *testing.T) {
backend := setupTestStorageBackend(t)
ctx := context.Background()
// Create a resource
testObj, err := createTestObjectWithName("test-resource", "apps", "test-data")
require.NoError(t, err)
metaAccessor, err := utils.MetaAccessor(testObj)
require.NoError(t, err)
writeEvent := WriteEvent{
Type: resourcepb.WatchEvent_ADDED,
Key: &resourcepb.ResourceKey{
Namespace: "default",
Group: "apps",
Resource: "resources",
Name: "test-resource",
},
Value: objectToJSONBytes(t, testObj),
Object: metaAccessor,
PreviousRV: 0,
}
rv1, err := backend.WriteEvent(ctx, writeEvent)
require.NoError(t, err)
// Update the resource prunerMaxEvents times. This will create one more event than the pruner limit.
previousRV := rv1
for i := 0; i < prunerMaxEvents; i++ {
testObj.Object["spec"].(map[string]any)["value"] = fmt.Sprintf("update-%d", i)
writeEvent.Type = resourcepb.WatchEvent_MODIFIED
writeEvent.Value = objectToJSONBytes(t, testObj)
writeEvent.PreviousRV = previousRV
newRv, err := backend.WriteEvent(ctx, writeEvent)
require.NoError(t, err)
previousRV = newRv
}
pruningKey := PruningKey{
Namespace: "default",
Group: "apps",
Resource: "resources",
Name: "test-resource",
}
err = backend.pruneEvents(ctx, pruningKey)
require.NoError(t, err)
// Verify the first event has been pruned (rv1)
eventKey1 := DataKey{
Namespace: "default",
Group: "apps",
Resource: "resources",
Name: "test-resource",
ResourceVersion: rv1,
}
_, err = backend.dataStore.Get(ctx, eventKey1)
require.Error(t, err) // Should return error as event is pruned
// assert prunerMaxEvents most recent events exist
counter := 0
for datakey, err := range backend.dataStore.Keys(ctx, ListRequestKey{
Namespace: "default",
Group: "apps",
Resource: "resources",
Name: "test-resource",
}) {
require.NoError(t, err)
require.NotEqual(t, rv1, datakey.ResourceVersion)
counter++
}
require.Equal(t, prunerMaxEvents, counter)
})
t.Run("will not prune events when less than limit", func(t *testing.T) {
backend := setupTestStorageBackend(t)
ctx := context.Background()
// Create a resource
testObj, err := createTestObjectWithName("test-resource", "apps", "test-data")
require.NoError(t, err)
metaAccessor, err := utils.MetaAccessor(testObj)
require.NoError(t, err)
writeEvent := WriteEvent{
Type: resourcepb.WatchEvent_ADDED,
Key: &resourcepb.ResourceKey{
Namespace: "default",
Group: "apps",
Resource: "resources",
Name: "test-resource",
},
Value: objectToJSONBytes(t, testObj),
Object: metaAccessor,
PreviousRV: 0,
}
rv1, err := backend.WriteEvent(ctx, writeEvent)
require.NoError(t, err)
// Update the resource prunerMaxEvents-1 times. This will create same number of events as the pruner limit.
previousRV := rv1
for i := 0; i < prunerMaxEvents-1; i++ {
testObj.Object["spec"].(map[string]any)["value"] = fmt.Sprintf("update-%d", i)
writeEvent.Type = resourcepb.WatchEvent_MODIFIED
writeEvent.Value = objectToJSONBytes(t, testObj)
writeEvent.PreviousRV = previousRV
newRv, err := backend.WriteEvent(ctx, writeEvent)
require.NoError(t, err)
previousRV = newRv
}
pruningKey := PruningKey{
Namespace: "default",
Group: "apps",
Resource: "resources",
Name: "test-resource",
}
err = backend.pruneEvents(ctx, pruningKey)
require.NoError(t, err)
// assert all events exist
counter := 0
for _, err := range backend.dataStore.Keys(ctx, ListRequestKey{
Namespace: "default",
Group: "apps",
Resource: "resources",
Name: "test-resource",
}) {
require.NoError(t, err)
counter++
}
require.Equal(t, prunerMaxEvents, counter)
})
}
// createTestObject creates a test unstructured object with standard values
func createTestObject() (*unstructured.Unstructured, error) {
return createTestObjectWithName("test-resource", appsNamespace, "test data")