Unistore: Fix dualwriter mode 3 (#108092)

* Unistore: Fix dualwriter mode 3

Fallback to legacy if Unified is the main store and we get an error when
reading from it

Signed-off-by: Maicon Costa <maiconscosta@gmail.com>


---------

Signed-off-by: Maicon Costa <maiconscosta@gmail.com>
This commit is contained in:
maicon
2025-07-15 11:16:26 -03:00
committed by GitHub
parent d39a47a89b
commit b017f42278
3 changed files with 32 additions and 5 deletions
+10 -1
View File
@@ -39,7 +39,16 @@ type dualWriter struct {
func (d *dualWriter) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
// If we read from unified, we can just do that and return.
if d.readUnified {
return d.unified.Get(ctx, name, options)
unifiedGet, unifiedErr := d.unified.Get(ctx, name, options)
if apierrors.IsNotFound(unifiedErr) {
// If resource is not found in unified storage, fallback to legacy.
// This fixes cases in where records (stored in multiple tables, including permissions)
// are inserted first in legacy and then on Unified.
log := logging.FromContext(ctx).With("method", "Get")
log.Error("resource not found in Unified Storage, trying to GET from legacy", "err", unifiedErr)
return d.legacy.Get(ctx, name, options)
}
return unifiedGet, unifiedErr
}
// If legacy is still our main store, lets first read from it.
legacyGet, err := d.legacy.Get(ctx, name, options)
@@ -109,12 +109,31 @@ func TestMode3_Get(t *testing.T) {
},
},
{
name: "should return an error when getting an object in the unified store fails, and should not go to legacy",
name: "should return an error when getting an object in the unified store fails",
setupStorageFn: func(m *mock.Mock, name string) {
m.On("Get", mock.Anything, name, mock.Anything).Return(nil, errors.New("error"))
},
wantErr: true,
},
{
name: "should return an error when getting an object in the unified store fails with not found error, and legacy fails as well",
setupLegacyFn: func(m *mock.Mock, name string) {
m.On("Get", mock.Anything, name, mock.Anything).Return(nil, errors.New("error"))
},
setupStorageFn: func(m *mock.Mock, name string) {
m.On("Get", mock.Anything, name, mock.Anything).Return(nil, apierrors.NewNotFound(schema.GroupResource{Group: "dashboards.dashboard.grafana.app", Resource: "dashboard"}, "uid"))
},
wantErr: true,
},
{
name: "should succeed when getting an object in the UnifiedStorage fails with NotFound, but Legacy succeeds",
setupLegacyFn: func(m *mock.Mock, name string) {
m.On("Get", mock.Anything, name, mock.Anything).Return(exampleObj, nil)
},
setupStorageFn: func(m *mock.Mock, name string) {
m.On("Get", mock.Anything, name, mock.Anything).Return(nil, apierrors.NewNotFound(schema.GroupResource{Group: "dashboards.dashboard.grafana.app", Resource: "dashboard"}, "uid"))
},
},
{
name: "should succeed when getting an object in the LegacyStorage fails",
setupLegacyFn: func(m *mock.Mock, name string) {
@@ -58,11 +58,10 @@ type TestContext struct {
// TestIntegrationValidation tests the dashboard K8s API
func TestIntegrationValidation(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test2")
t.Skip("skipping integration test")
}
// TODO: Skip mode3 - borken due to race conditions while setting default permissions across storage backends
dualWriterModes := []rest.DualWriterMode{rest.Mode0, rest.Mode1, rest.Mode2}
dualWriterModes := []rest.DualWriterMode{rest.Mode0, rest.Mode1, rest.Mode2, rest.Mode3, rest.Mode4, rest.Mode5}
for _, dualWriterMode := range dualWriterModes {
t.Run(fmt.Sprintf("DualWriterMode %d", dualWriterMode), func(t *testing.T) {
// Create a K8sTestHelper which will set up a real API server