The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/pkg/apiserver/rest/dualwriter_mode2_test.go

432 lines
12 KiB

package rest
import (
"context"
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)
var createFn = func(context.Context, runtime.Object) error { return nil }
var exampleOption = &metainternalversion.ListOptions{}
func TestMode2_Create(t *testing.T) {
type testCase struct {
input runtime.Object
setupLegacyFn func(m *mock.Mock, input runtime.Object)
setupStorageFn func(m *mock.Mock, input runtime.Object)
name string
wantErr bool
}
tests :=
[]testCase{
{
name: "creating an object in both the LegacyStorage and Storage",
input: exampleObj,
setupLegacyFn: func(m *mock.Mock, input runtime.Object) {
m.On("Create", mock.Anything, exampleObjNoRV, mock.Anything, mock.Anything).Return(exampleObj, nil)
},
setupStorageFn: func(m *mock.Mock, input runtime.Object) {
m.On("Create", mock.Anything, exampleObj, mock.Anything, mock.Anything).Return(exampleObj, 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, input, 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, tt.input)
}
dw := NewDualWriter(Mode2, ls, us, p, kind)
obj, err := dw.Create(context.Background(), tt.input, createFn, &metav1.CreateOptions{})
if tt.wantErr {
assert.Error(t, err)
return
}
assert.Equal(t, exampleObj, obj)
})
}
}
func TestMode2_Get(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: "getting an object from storage",
input: "foo",
setupLegacyFn: 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("Get", mock.Anything, input, mock.Anything).Return(anotherObj, nil)
},
},
{
name: "object not present in storage but present in legacy store",
input: "foo",
setupLegacyFn: 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("Get", mock.Anything, input, mock.Anything).Return(nil, errors.New("error"))
},
},
{
name: "error when getting object in both stores fails",
input: "object-fail",
setupLegacyFn: func(m *mock.Mock, input string) {
m.On("Get", mock.Anything, input, mock.Anything).Return(nil, errors.New("error"))
},
setupStorageFn: func(m *mock.Mock, input string) {
m.On("Get", 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(Mode2, ls, us, p, kind)
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 TestMode2_List(t *testing.T) {
type testCase struct {
inputLegacy *metainternalversion.ListOptions
setupLegacyFn func(m *mock.Mock)
setupStorageFn func(m *mock.Mock)
name string
wantErr bool
}
tests :=
[]testCase{
{
name: "object present in both Storage and LegacyStorage",
inputLegacy: exampleOption,
setupLegacyFn: func(m *mock.Mock) {
m.On("List", mock.Anything, mock.Anything).Return(exampleList, nil)
},
setupStorageFn: func(m *mock.Mock) {
m.On("List", mock.Anything, mock.Anything).Return(anotherList, 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.setupLegacyFn != nil {
tt.setupLegacyFn(m)
}
if tt.setupStorageFn != nil {
tt.setupStorageFn(m)
}
dw := NewDualWriter(Mode2, ls, us, p, kind)
obj, err := dw.List(context.Background(), &metainternalversion.ListOptions{})
if tt.wantErr {
assert.Error(t, err)
return
}
assert.Equal(t, exampleList, obj)
})
}
}
func TestMode2_Delete(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: "delete in legacy and storage",
input: "foo",
setupLegacyFn: func(m *mock.Mock, input string) {
m.On("Delete", mock.Anything, input, mock.Anything, mock.Anything).Return(exampleObj, false, nil)
},
setupStorageFn: func(m *mock.Mock, input string) {
m.On("Delete", mock.Anything, input, mock.Anything, mock.Anything).Return(exampleObj, false, nil)
},
},
{
name: "object delete in legacy not found, but found in storage",
input: "foo",
setupLegacyFn: func(m *mock.Mock, input string) {
m.On("Delete", mock.Anything, "not-found-legacy", mock.Anything, mock.Anything).Return(nil, false, apierrors.NewNotFound(schema.GroupResource{Group: "", Resource: "pods"}, "not-found"))
},
setupStorageFn: func(m *mock.Mock, input string) {
m.On("Delete", mock.Anything, input, mock.Anything, mock.Anything).Return(exampleObj, false, nil)
},
},
{
name: " object delete in storage not found, but found in legacy",
input: "foo",
setupLegacyFn: func(m *mock.Mock, input string) {
m.On("Delete", mock.Anything, input, mock.Anything, mock.Anything).Return(exampleObj, false, nil)
},
setupStorageFn: func(m *mock.Mock, input string) {
m.On("Delete", mock.Anything, "not-found-storage", mock.Anything, mock.Anything).Return(nil, false, apierrors.NewNotFound(schema.GroupResource{Group: "", Resource: "pods"}, "not-found"))
},
},
{
name: " object not found in both",
input: "object-fail",
setupLegacyFn: func(m *mock.Mock, input string) {
m.On("Delete", mock.Anything, input, mock.Anything, mock.Anything).Return(nil, false, apierrors.NewNotFound(schema.GroupResource{Group: "", Resource: "pods"}, "not-found"))
},
setupStorageFn: func(m *mock.Mock, input string) {
m.On("Delete", mock.Anything, input, mock.Anything, mock.Anything).Return(nil, false, apierrors.NewNotFound(schema.GroupResource{Group: "", Resource: "pods"}, "not-found"))
},
wantErr: true,
},
{
name: " object delete error",
input: "object-fail",
setupLegacyFn: func(m *mock.Mock, input string) {
m.On("Delete", mock.Anything, input, mock.Anything, mock.Anything).Return(nil, false, errors.New("error"))
},
setupStorageFn: func(m *mock.Mock, input string) {
m.On("Delete", mock.Anything, input, 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(Mode2, ls, us, p, kind)
obj, _, err := dw.Delete(context.Background(), tt.input, func(context.Context, 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 TestMode2_DeleteCollection(t *testing.T) {
type testCase struct {
setupLegacyFn func(m *mock.Mock)
setupStorageFn func(m *mock.Mock)
name string
input string
wantErr bool
}
tests :=
[]testCase{
{
name: "deleting a collection in both stores",
input: "foo",
setupLegacyFn: func(m *mock.Mock) {
m.On("DeleteCollection", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(exampleList, nil)
},
setupStorageFn: func(m *mock.Mock) {
m.On("DeleteCollection", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(exampleList, nil)
},
},
{
name: "error deleting a collection in the storage when legacy store is successful",
input: "foo",
setupLegacyFn: func(m *mock.Mock) {
m.On("DeleteCollection", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(exampleObj, nil)
},
setupStorageFn: func(m *mock.Mock) {
m.On("DeleteCollection", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, errors.New("error"))
},
wantErr: true,
},
{
name: "deleting a collection when error in legacy store",
input: "fail",
setupLegacyFn: func(m *mock.Mock) {
m.On("DeleteCollection", mock.Anything, mock.Anything, &metav1.DeleteOptions{TypeMeta: metav1.TypeMeta{Kind: "fail"}}, 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)
}
if tt.setupStorageFn != nil {
tt.setupStorageFn(m)
}
dw := NewDualWriter(Mode2, ls, us, p, kind)
obj, err := dw.DeleteCollection(context.Background(), func(ctx context.Context, obj runtime.Object) error { return nil }, &metav1.DeleteOptions{TypeMeta: metav1.TypeMeta{Kind: tt.input}}, &metainternalversion.ListOptions{})
if tt.wantErr {
assert.Error(t, err)
return
}
assert.Equal(t, exampleList, obj)
})
}
}
func TestMode2_Update(t *testing.T) {
type testCase struct {
expectedObj runtime.Object
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 both stores",
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(exampleObj, false, nil)
},
expectedObj: exampleObj,
},
{
name: "error updating legacy store",
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"))
},
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(Mode2, ls, us, p, kind)
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, tt.expectedObj, obj)
assert.NotEqual(t, anotherObj, obj)
})
}
}