mirror of https://github.com/grafana/grafana
Dual writer: mode 3 (#90045)
* Dual writer: mode 3 * Add integration tests for playlits in mode 3 * Remove todo * Update pkg/apiserver/rest/dualwriter_mode3.go Co-authored-by: Arati R. <33031346+suntala@users.noreply.github.com> * Admin: Fixes an issue where user accounts could not be enabled (#88117) Fix: unable to enable user * [REVIEW] FInish mode 3 and add tests * Improve logging * Update dependencies * Update pkg/apiserver/rest/dualwriter_mode3_test.go Co-authored-by: maicon <maiconscosta@gmail.com> * remove test assertion * Use mode log when dual writer is initiated --------- Co-authored-by: Arati R. <33031346+suntala@users.noreply.github.com> Co-authored-by: gonvee <gonvee@qq.com> Co-authored-by: maicon <maiconscosta@gmail.com>pull/90882/head
parent
5e3a5b355e
commit
67b74e1e8a
@ -1,72 +1,356 @@ |
||||
package rest |
||||
|
||||
// import (
|
||||
// "context"
|
||||
// "testing"
|
||||
|
||||
// "github.com/stretchr/testify/assert"
|
||||
// metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
|
||||
// metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
// "k8s.io/apimachinery/pkg/runtime"
|
||||
// "k8s.io/apiserver/pkg/apis/example"
|
||||
// )
|
||||
|
||||
// func TestMode3(t *testing.T) {
|
||||
// var ls = (LegacyStorage)(nil)
|
||||
// var s = (Storage)(nil)
|
||||
// lsSpy := NewLegacyStorageSpyClient(ls)
|
||||
// sSpy := NewStorageSpyClient(s)
|
||||
|
||||
// dw := NewDualWriterMode3(lsSpy, sSpy)
|
||||
|
||||
// // Create: it should use the Legacy Create implementation
|
||||
// _, err := dw.Create(context.Background(), &dummyObject{}, func(context.Context, runtime.Object) error { return nil }, &metav1.CreateOptions{})
|
||||
// assert.NoError(t, err)
|
||||
// assert.Equal(t, 1, lsSpy.Counts("LegacyStorage.Create"))
|
||||
// assert.Equal(t, 1, sSpy.Counts("Storage.Create"))
|
||||
|
||||
// // Get: it should use the Storage Get implementation
|
||||
// _, err = dw.Get(context.Background(), kind, &metav1.GetOptions{})
|
||||
// assert.NoError(t, err)
|
||||
// assert.Equal(t, 0, lsSpy.Counts("LegacyStorage.Get"))
|
||||
// assert.Equal(t, 1, sSpy.Counts("Storage.Get"))
|
||||
|
||||
// // List: it should use the Storage List implementation
|
||||
// _, err = dw.List(context.Background(), &metainternalversion.ListOptions{})
|
||||
// assert.NoError(t, err)
|
||||
// assert.Equal(t, 0, lsSpy.Counts("LegacyStorage.List"))
|
||||
// assert.Equal(t, 1, sSpy.Counts("Storage.List"))
|
||||
|
||||
// // Delete: it should use call both Legacy and Storage Delete methods
|
||||
// var deleteValidation = func(ctx context.Context, obj runtime.Object) error { return nil }
|
||||
// _, _, err = dw.Delete(context.Background(), kind, deleteValidation, &metav1.DeleteOptions{})
|
||||
// assert.NoError(t, err)
|
||||
// assert.Equal(t, 1, lsSpy.Counts("LegacyStorage.Delete"))
|
||||
// assert.Equal(t, 1, sSpy.Counts("Storage.Delete"))
|
||||
|
||||
// // DeleteCollection: it should delete from both LegacyStorage and Storage
|
||||
// _, err = dw.DeleteCollection(
|
||||
// context.Background(),
|
||||
// func(context.Context, runtime.Object) error { return nil },
|
||||
// &metav1.DeleteOptions{},
|
||||
// &metainternalversion.ListOptions{},
|
||||
// )
|
||||
// assert.NoError(t, err)
|
||||
// assert.Equal(t, 1, lsSpy.Counts("LegacyStorage.DeleteCollection"))
|
||||
// assert.Equal(t, 1, sSpy.Counts("Storage.DeleteCollection"))
|
||||
|
||||
// // Update: it should update in both storages
|
||||
// dummy := &example.Pod{}
|
||||
// uoi := UpdatedObjInfoObj{}
|
||||
// _, err = uoi.UpdatedObject(context.Background(), dummy)
|
||||
// assert.NoError(t, err)
|
||||
|
||||
// var validateObjFn = func(ctx context.Context, obj runtime.Object) error { return nil }
|
||||
// var validateObjUpdateFn = func(ctx context.Context, obj, old runtime.Object) error { return nil }
|
||||
|
||||
// _, _, err = dw.Update(context.Background(), kind, uoi, validateObjFn, validateObjUpdateFn, false, &metav1.UpdateOptions{})
|
||||
// assert.NoError(t, err)
|
||||
// assert.Equal(t, 1, lsSpy.Counts("LegacyStorage.Update"))
|
||||
// assert.Equal(t, 1, sSpy.Counts("Storage.Update"))
|
||||
// assert.NoError(t, err)
|
||||
// }
|
||||
import ( |
||||
"context" |
||||
"errors" |
||||
"testing" |
||||
|
||||
"github.com/prometheus/client_golang/prometheus" |
||||
"github.com/stretchr/testify/assert" |
||||
"github.com/stretchr/testify/mock" |
||||
"k8s.io/apimachinery/pkg/api/meta" |
||||
metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" |
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" |
||||
"k8s.io/apimachinery/pkg/runtime" |
||||
) |
||||
|
||||
func TestMode3_Create(t *testing.T) { |
||||
type testCase struct { |
||||
input runtime.Object |
||||
setupLegacyFn func(m *mock.Mock, input runtime.Object) |
||||
setupStorageFn func(m *mock.Mock) |
||||
name string |
||||
wantErr bool |
||||
} |
||||
tests := |
||||
[]testCase{ |
||||
{ |
||||
name: "creating an object only in the unified store", |
||||
input: exampleObj, |
||||
setupLegacyFn: func(m *mock.Mock, input runtime.Object) { |
||||
m.On("Create", mock.Anything, input, mock.Anything, mock.Anything).Return(exampleObj, nil) |
||||
}, |
||||
setupStorageFn: func(m *mock.Mock) { |
||||
m.On("Create", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(exampleObj, nil) |
||||
}, |
||||
}, |
||||
{ |
||||
name: "error when creating object in the unified store fails", |
||||
input: failingObj, |
||||
setupLegacyFn: func(m *mock.Mock, input runtime.Object) { |
||||
m.On("Create", mock.Anything, failingObj, mock.Anything, mock.Anything).Return(nil, errors.New("error")) |
||||
}, |
||||
wantErr: true, |
||||
}, |
||||
} |
||||
|
||||
for _, tt := range tests { |
||||
t.Run(tt.name, func(t *testing.T) { |
||||
l := (LegacyStorage)(nil) |
||||
s := (Storage)(nil) |
||||
m := &mock.Mock{} |
||||
|
||||
ls := legacyStoreMock{m, l} |
||||
us := storageMock{m, s} |
||||
|
||||
if tt.setupLegacyFn != nil { |
||||
tt.setupLegacyFn(m, tt.input) |
||||
} |
||||
if tt.setupStorageFn != nil { |
||||
tt.setupStorageFn(m) |
||||
} |
||||
|
||||
dw := NewDualWriter(Mode3, ls, us, p) |
||||
|
||||
obj, err := dw.Create(context.Background(), tt.input, func(context.Context, runtime.Object) error { return nil }, &metav1.CreateOptions{}) |
||||
|
||||
if tt.wantErr { |
||||
assert.Error(t, err) |
||||
return |
||||
} |
||||
|
||||
acc, err := meta.Accessor(obj) |
||||
assert.NoError(t, err) |
||||
assert.Equal(t, acc.GetResourceVersion(), "1") |
||||
assert.NotEqual(t, obj, anotherObj) |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func TestMode3_Get(t *testing.T) { |
||||
type testCase struct { |
||||
setupStorageFn func(m *mock.Mock, name string) |
||||
name string |
||||
input string |
||||
wantErr bool |
||||
} |
||||
tests := |
||||
[]testCase{ |
||||
{ |
||||
name: "get an object only in unified store", |
||||
input: "foo", |
||||
setupStorageFn: func(m *mock.Mock, name string) { |
||||
m.On("Get", mock.Anything, name, mock.Anything).Return(exampleObj, nil) |
||||
}, |
||||
}, |
||||
{ |
||||
name: "error when getting an object in the unified store fails", |
||||
input: "object-fail", |
||||
setupStorageFn: func(m *mock.Mock, name string) { |
||||
m.On("Get", mock.Anything, name, mock.Anything).Return(nil, errors.New("error")) |
||||
}, |
||||
wantErr: true, |
||||
}, |
||||
} |
||||
|
||||
for _, tt := range tests { |
||||
t.Run(tt.name, func(t *testing.T) { |
||||
l := (LegacyStorage)(nil) |
||||
s := (Storage)(nil) |
||||
m := &mock.Mock{} |
||||
|
||||
ls := legacyStoreMock{m, l} |
||||
us := storageMock{m, s} |
||||
|
||||
if tt.setupStorageFn != nil { |
||||
tt.setupStorageFn(m, tt.input) |
||||
} |
||||
|
||||
p := prometheus.NewRegistry() |
||||
dw := NewDualWriter(Mode3, ls, us, p) |
||||
|
||||
obj, err := dw.Get(context.Background(), tt.input, &metav1.GetOptions{}) |
||||
|
||||
if tt.wantErr { |
||||
assert.Error(t, err) |
||||
return |
||||
} |
||||
|
||||
assert.Equal(t, obj, exampleObj) |
||||
assert.NotEqual(t, obj, anotherObj) |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func TestMode3_List(t *testing.T) { |
||||
type testCase struct { |
||||
setupStorageFn func(m *mock.Mock, options *metainternalversion.ListOptions) |
||||
name string |
||||
options *metainternalversion.ListOptions |
||||
wantErr bool |
||||
} |
||||
tests := |
||||
[]testCase{ |
||||
{ |
||||
name: "error when listing an object in the unified store is not implemented", |
||||
options: &metainternalversion.ListOptions{TypeMeta: metav1.TypeMeta{Kind: "fail"}}, |
||||
setupStorageFn: func(m *mock.Mock, options *metainternalversion.ListOptions) { |
||||
m.On("List", mock.Anything, options).Return(nil, errors.New("error")) |
||||
}, |
||||
wantErr: true, |
||||
}, |
||||
{ |
||||
name: "list objects in the unified store", |
||||
options: &metainternalversion.ListOptions{TypeMeta: metav1.TypeMeta{Kind: "foo"}}, |
||||
setupStorageFn: func(m *mock.Mock, options *metainternalversion.ListOptions) { |
||||
m.On("List", mock.Anything, options).Return(exampleList, nil) |
||||
}, |
||||
}, |
||||
} |
||||
|
||||
for _, tt := range tests { |
||||
t.Run(tt.name, func(t *testing.T) { |
||||
l := (LegacyStorage)(nil) |
||||
s := (Storage)(nil) |
||||
m := &mock.Mock{} |
||||
|
||||
ls := legacyStoreMock{m, l} |
||||
us := storageMock{m, s} |
||||
|
||||
if tt.setupStorageFn != nil { |
||||
tt.setupStorageFn(m, tt.options) |
||||
} |
||||
|
||||
dw := NewDualWriter(Mode3, ls, us, p) |
||||
|
||||
res, err := dw.List(context.Background(), tt.options) |
||||
|
||||
if tt.wantErr { |
||||
assert.Error(t, err) |
||||
return |
||||
} |
||||
|
||||
assert.Equal(t, exampleList, res) |
||||
assert.NotEqual(t, anotherList, res) |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func TestMode3_Delete(t *testing.T) { |
||||
type testCase struct { |
||||
setupStorageFn func(m *mock.Mock, name string) |
||||
name string |
||||
input string |
||||
wantErr bool |
||||
} |
||||
tests := |
||||
[]testCase{ |
||||
{ |
||||
name: "deleting an object in the unified store", |
||||
input: "foo", |
||||
setupStorageFn: func(m *mock.Mock, name string) { |
||||
m.On("Delete", mock.Anything, name, mock.Anything, mock.Anything).Return(exampleObj, false, nil) |
||||
}, |
||||
}, |
||||
{ |
||||
name: "error when deleting an object in the unified store", |
||||
input: "object-fail", |
||||
setupStorageFn: func(m *mock.Mock, name string) { |
||||
m.On("Delete", mock.Anything, name, mock.Anything, mock.Anything).Return(nil, false, errors.New("error")) |
||||
}, |
||||
wantErr: true, |
||||
}, |
||||
} |
||||
|
||||
for _, tt := range tests { |
||||
t.Run(tt.name, func(t *testing.T) { |
||||
l := (LegacyStorage)(nil) |
||||
s := (Storage)(nil) |
||||
m := &mock.Mock{} |
||||
|
||||
ls := legacyStoreMock{m, l} |
||||
us := storageMock{m, s} |
||||
|
||||
if tt.setupStorageFn != nil { |
||||
tt.setupStorageFn(m, tt.input) |
||||
} |
||||
|
||||
dw := NewDualWriter(Mode3, ls, us, p) |
||||
|
||||
obj, _, err := dw.Delete(context.Background(), tt.input, func(ctx context.Context, obj runtime.Object) error { return nil }, &metav1.DeleteOptions{}) |
||||
|
||||
if tt.wantErr { |
||||
assert.Error(t, err) |
||||
return |
||||
} |
||||
|
||||
assert.Equal(t, obj, exampleObj) |
||||
assert.NotEqual(t, obj, anotherObj) |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func TestMode3_DeleteCollection(t *testing.T) { |
||||
type testCase struct { |
||||
input *metav1.DeleteOptions |
||||
setupStorageFn func(m *mock.Mock, input *metav1.DeleteOptions) |
||||
name string |
||||
wantErr bool |
||||
} |
||||
tests := |
||||
[]testCase{ |
||||
{ |
||||
name: "deleting a collection in the unified store", |
||||
input: &metav1.DeleteOptions{TypeMeta: metav1.TypeMeta{Kind: "foo"}}, |
||||
setupStorageFn: func(m *mock.Mock, input *metav1.DeleteOptions) { |
||||
m.On("DeleteCollection", mock.Anything, mock.Anything, input, mock.Anything).Return(exampleObj, nil) |
||||
}, |
||||
}, |
||||
{ |
||||
name: "error deleting a collection in the unified store", |
||||
input: &metav1.DeleteOptions{TypeMeta: metav1.TypeMeta{Kind: "fail"}}, |
||||
setupStorageFn: func(m *mock.Mock, input *metav1.DeleteOptions) { |
||||
m.On("DeleteCollection", mock.Anything, mock.Anything, input, mock.Anything).Return(nil, errors.New("error")) |
||||
}, |
||||
wantErr: true, |
||||
}, |
||||
} |
||||
|
||||
for _, tt := range tests { |
||||
t.Run(tt.name, func(t *testing.T) { |
||||
l := (LegacyStorage)(nil) |
||||
s := (Storage)(nil) |
||||
m := &mock.Mock{} |
||||
|
||||
ls := legacyStoreMock{m, l} |
||||
us := storageMock{m, s} |
||||
|
||||
if tt.setupStorageFn != nil { |
||||
tt.setupStorageFn(m, tt.input) |
||||
} |
||||
|
||||
dw := NewDualWriter(Mode3, ls, us, p) |
||||
|
||||
obj, err := dw.DeleteCollection(context.Background(), func(ctx context.Context, obj runtime.Object) error { return nil }, tt.input, &metainternalversion.ListOptions{}) |
||||
|
||||
if tt.wantErr { |
||||
assert.Error(t, err) |
||||
return |
||||
} |
||||
|
||||
assert.Equal(t, obj, exampleObj) |
||||
assert.NotEqual(t, obj, anotherObj) |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func TestMode3_Update(t *testing.T) { |
||||
type testCase struct { |
||||
setupLegacyFn func(m *mock.Mock, input string) |
||||
setupStorageFn func(m *mock.Mock, input string) |
||||
name string |
||||
input string |
||||
wantErr bool |
||||
} |
||||
tests := |
||||
[]testCase{ |
||||
{ |
||||
name: "update an object in unified store", |
||||
input: "foo", |
||||
setupStorageFn: func(m *mock.Mock, input string) { |
||||
m.On("Update", mock.Anything, input, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(exampleObj, false, nil) |
||||
}, |
||||
setupLegacyFn: func(m *mock.Mock, input string) { |
||||
m.On("Update", mock.Anything, input, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(exampleObj, false, nil) |
||||
}, |
||||
}, |
||||
{ |
||||
name: "error updating an object in unified store", |
||||
input: "object-fail", |
||||
setupStorageFn: func(m *mock.Mock, input string) { |
||||
m.On("Update", mock.Anything, input, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, false, errors.New("error")) |
||||
}, |
||||
wantErr: true, |
||||
}, |
||||
} |
||||
|
||||
for _, tt := range tests { |
||||
t.Run(tt.name, func(t *testing.T) { |
||||
l := (LegacyStorage)(nil) |
||||
s := (Storage)(nil) |
||||
m := &mock.Mock{} |
||||
|
||||
ls := legacyStoreMock{m, l} |
||||
us := storageMock{m, s} |
||||
|
||||
if tt.setupLegacyFn != nil { |
||||
tt.setupLegacyFn(m, tt.input) |
||||
} |
||||
if tt.setupStorageFn != nil { |
||||
tt.setupStorageFn(m, tt.input) |
||||
} |
||||
|
||||
dw := NewDualWriter(Mode3, ls, us, p) |
||||
|
||||
obj, _, err := dw.Update(context.Background(), tt.input, updatedObjInfoObj{}, func(ctx context.Context, obj runtime.Object) error { return nil }, func(ctx context.Context, obj, old runtime.Object) error { return nil }, false, &metav1.UpdateOptions{}) |
||||
|
||||
if tt.wantErr { |
||||
assert.Error(t, err) |
||||
return |
||||
} |
||||
|
||||
assert.Equal(t, obj, exampleObj) |
||||
assert.NotEqual(t, obj, anotherObj) |
||||
}) |
||||
} |
||||
} |
||||
|
Loading…
Reference in new issue