mirror of
https://github.com/grafana/grafana.git
synced 2025-01-09 15:43:23 -06:00
67b74e1e8a
* 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>
393 lines
12 KiB
Go
393 lines
12 KiB
Go
package rest
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
|
|
"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"
|
|
"k8s.io/apiserver/pkg/apis/example"
|
|
)
|
|
|
|
var exampleObj = &example.Pod{TypeMeta: metav1.TypeMeta{Kind: "foo"}, ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: "1", CreationTimestamp: metav1.Time{}}, Spec: example.PodSpec{}, Status: example.PodStatus{StartTime: &metav1.Time{Time: time.Now()}}}
|
|
var exampleObjNoRV = &example.Pod{TypeMeta: metav1.TypeMeta{Kind: "foo"}, ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: "", CreationTimestamp: metav1.Time{}}, Spec: example.PodSpec{}, Status: example.PodStatus{StartTime: &metav1.Time{Time: time.Now()}}}
|
|
var exampleObjDifferentRV = &example.Pod{TypeMeta: metav1.TypeMeta{Kind: "foo"}, ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: "3"}, Spec: example.PodSpec{}, Status: example.PodStatus{}}
|
|
var anotherObj = &example.Pod{TypeMeta: metav1.TypeMeta{Kind: "foo"}, ObjectMeta: metav1.ObjectMeta{Name: "bar", ResourceVersion: "2"}, Spec: example.PodSpec{}, Status: example.PodStatus{StartTime: &metav1.Time{Time: time.Now()}}}
|
|
var failingObj = &example.Pod{TypeMeta: metav1.TypeMeta{Kind: "foo"}, ObjectMeta: metav1.ObjectMeta{Name: "object-fail", ResourceVersion: "2"}, Spec: example.PodSpec{}, Status: example.PodStatus{}}
|
|
var exampleList = &example.PodList{TypeMeta: metav1.TypeMeta{Kind: "foo"}, ListMeta: metav1.ListMeta{}, Items: []example.Pod{*exampleObj}}
|
|
var anotherList = &example.PodList{Items: []example.Pod{*anotherObj}}
|
|
|
|
var p = prometheus.NewRegistry()
|
|
|
|
func TestMode1_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 legacy 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(exampleObjNoRV, nil)
|
|
},
|
|
},
|
|
{
|
|
name: "error when creating object in the legacy 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(Mode1, 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 TestMode1_Get(t *testing.T) {
|
|
type testCase struct {
|
|
setupLegacyFn func(m *mock.Mock, name string)
|
|
setupStorageFn func(m *mock.Mock, name string)
|
|
name string
|
|
input string
|
|
wantErr bool
|
|
}
|
|
tests :=
|
|
[]testCase{
|
|
{
|
|
name: "get an object only in the legacy store",
|
|
input: "foo",
|
|
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(anotherObj, nil)
|
|
},
|
|
},
|
|
{
|
|
name: "error when getting an object in the legacy store fails",
|
|
input: "object-fail",
|
|
setupLegacyFn: 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.setupLegacyFn != nil {
|
|
tt.setupLegacyFn(m, tt.input)
|
|
}
|
|
if tt.setupStorageFn != nil {
|
|
tt.setupStorageFn(m, tt.input)
|
|
}
|
|
|
|
dw := NewDualWriter(Mode1, ls, us, p)
|
|
|
|
obj, err := dw.Get(context.Background(), tt.input, &metav1.GetOptions{})
|
|
|
|
if tt.wantErr {
|
|
assert.Error(t, err)
|
|
return
|
|
}
|
|
|
|
us.AssertNotCalled(t, "Get", context.Background(), tt.name, &metav1.GetOptions{})
|
|
|
|
assert.Equal(t, obj, exampleObj)
|
|
assert.NotEqual(t, obj, anotherObj)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMode1_List(t *testing.T) {
|
|
type testCase struct {
|
|
setupLegacyFn func(m *mock.Mock)
|
|
setupStorageFn func(m *mock.Mock)
|
|
name string
|
|
wantErr bool
|
|
}
|
|
tests :=
|
|
[]testCase{
|
|
{
|
|
name: "error when listing an object in the legacy store is not implemented",
|
|
setupLegacyFn: func(m *mock.Mock) {
|
|
m.On("List", mock.Anything, mock.Anything).Return(&example.PodList{}, errors.New("error"))
|
|
},
|
|
},
|
|
// TODO: legacy list is missing
|
|
}
|
|
|
|
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)
|
|
}
|
|
if tt.setupStorageFn != nil {
|
|
tt.setupStorageFn(m)
|
|
}
|
|
|
|
dw := NewDualWriter(Mode1, ls, us, p)
|
|
|
|
_, err := dw.List(context.Background(), &metainternalversion.ListOptions{})
|
|
|
|
if tt.wantErr {
|
|
assert.Error(t, err)
|
|
return
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMode1_Delete(t *testing.T) {
|
|
type testCase struct {
|
|
setupLegacyFn func(m *mock.Mock, name string)
|
|
setupStorageFn func(m *mock.Mock, name string)
|
|
name string
|
|
input string
|
|
wantErr bool
|
|
}
|
|
tests :=
|
|
[]testCase{
|
|
{
|
|
name: "deleting an object in the legacy store",
|
|
input: "foo",
|
|
setupLegacyFn: 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 legacy store",
|
|
input: "object-fail",
|
|
setupLegacyFn: 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.setupLegacyFn != nil {
|
|
tt.setupLegacyFn(m, tt.input)
|
|
}
|
|
if tt.setupStorageFn != nil {
|
|
tt.setupStorageFn(m, tt.input)
|
|
}
|
|
|
|
dw := NewDualWriter(Mode1, 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
|
|
}
|
|
|
|
us.AssertNotCalled(t, "Delete", context.Background(), tt.input, func(ctx context.Context, obj runtime.Object) error { return nil }, &metav1.DeleteOptions{})
|
|
assert.Equal(t, obj, exampleObj)
|
|
assert.NotEqual(t, obj, anotherObj)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMode1_DeleteCollection(t *testing.T) {
|
|
type testCase struct {
|
|
input *metav1.DeleteOptions
|
|
setupLegacyFn func(m *mock.Mock, input *metav1.DeleteOptions)
|
|
setupStorageFn func(m *mock.Mock, input *metav1.DeleteOptions)
|
|
name string
|
|
wantErr bool
|
|
}
|
|
tests :=
|
|
[]testCase{
|
|
{
|
|
name: "deleting a collection in the legacy store",
|
|
input: &metav1.DeleteOptions{TypeMeta: metav1.TypeMeta{Kind: "foo"}},
|
|
setupLegacyFn: 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 legacy store",
|
|
input: &metav1.DeleteOptions{TypeMeta: metav1.TypeMeta{Kind: "fail"}},
|
|
setupLegacyFn: 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.setupLegacyFn != nil {
|
|
tt.setupLegacyFn(m, tt.input)
|
|
}
|
|
if tt.setupStorageFn != nil {
|
|
tt.setupStorageFn(m, tt.input)
|
|
}
|
|
|
|
dw := NewDualWriter(Mode1, 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
|
|
}
|
|
|
|
us.AssertNotCalled(t, "DeleteCollection", context.Background(), tt.input, func(ctx context.Context, obj runtime.Object) error { return nil }, &metav1.DeleteOptions{})
|
|
assert.Equal(t, obj, exampleObj)
|
|
assert.NotEqual(t, obj, anotherObj)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMode1_Update(t *testing.T) {
|
|
type testCase struct {
|
|
setupLegacyFn func(m *mock.Mock, input string)
|
|
setupStorageFn func(m *mock.Mock, input string)
|
|
setupGetFn func(m *mock.Mock, input string)
|
|
name string
|
|
input string
|
|
wantErr bool
|
|
}
|
|
tests :=
|
|
[]testCase{
|
|
{
|
|
name: "update an object in legacy",
|
|
input: "foo",
|
|
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)
|
|
},
|
|
setupStorageFn: func(m *mock.Mock, input string) {
|
|
m.On("Update", mock.Anything, input, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(anotherObj, false, nil)
|
|
},
|
|
setupGetFn: func(m *mock.Mock, input string) {
|
|
m.On("Get", mock.Anything, input, mock.Anything).Return(exampleObj, nil)
|
|
},
|
|
},
|
|
{
|
|
name: "error updating an object in legacy",
|
|
input: "object-fail",
|
|
setupLegacyFn: 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"))
|
|
},
|
|
setupGetFn: func(m *mock.Mock, input string) {
|
|
m.On("Get", mock.Anything, input, mock.Anything).Return(exampleObj, nil)
|
|
},
|
|
setupStorageFn: func(m *mock.Mock, input string) {
|
|
m.On("Update", mock.Anything, input, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(anotherObj, false, nil)
|
|
},
|
|
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)
|
|
}
|
|
|
|
if tt.setupGetFn != nil {
|
|
tt.setupGetFn(m, tt.input)
|
|
}
|
|
|
|
dw := NewDualWriter(Mode1, 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)
|
|
})
|
|
}
|
|
}
|