Merge remote-tracking branch 'origin/main' into grafana-git-ui-sync

This commit is contained in:
Mariell Hoversholm committed 2025-02-12 10:11:16 +01:00
commit a2f6094bb9
9 files changed
+695 -490

No files matched your search

@@ -2,13 +2,19 @@ package responsewriter
import (
"bufio"
"context"
"errors"
"fmt"
"io"
"net/http"
"sync/atomic"
"github.com/grafana/grafana-app-sdk/logging"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/endpoints/responsewriter"
"k8s.io/component-base/tracing"
"k8s.io/klog/v2"
)
@@ -27,6 +33,13 @@ func WrapHandler(handler http.Handler) func(req *http.Request) (*http.Response,
// so the client will be responsible for closing the response body.
//nolint:bodyclose
return func(req *http.Request) (*http.Response, error) {
ctx, cancel, err := createLimitedContext(req)
if err != nil {
return nil, err
}
defer cancel()
req = req.WithContext(ctx) // returns a shallow copy, so we can't do it as part of the adapter.
w := NewAdapter(req)
go func() {
handler.ServeHTTP(w, req)
@@ -39,6 +52,74 @@ func WrapHandler(handler http.Handler) func(req *http.Request) (*http.Response,
}
}
// createLimitedContext creates a new context based on the the req's.
// It contains vital information such as a logger for the driver of the request, a user for auth, tracing, and deadlines. It propagates the parent's cancellation.
func createLimitedContext(req *http.Request) (context.Context, context.CancelFunc, error) {
refCtx := req.Context()
newCtx := context.Background()
if ns, ok := request.NamespaceFrom(refCtx); ok {
newCtx = request.WithNamespace(newCtx, ns)
}
if signal := request.ServerShutdownSignalFrom(refCtx); signal != nil {
newCtx = request.WithServerShutdownSignal(newCtx, signal)
}
requester, _ := identity.GetRequester(refCtx)
if requester != nil {
newCtx = identity.WithRequester(newCtx, requester)
}
usr, ok := request.UserFrom(refCtx)
if !ok && requester != nil {
// add in k8s user if not there yet
var ok bool
usr, ok = requester.(user.Info)
if !ok {
return nil, nil, fmt.Errorf("could not convert user to Kubernetes user")
}
}
if ok {
newCtx = request.WithUser(newCtx, usr)
}
// App SDK logger
appLogger := logging.FromContext(refCtx)
newCtx = logging.Context(newCtx, appLogger)
// Klog logger
klogger := klog.FromContext(refCtx)
if klogger.Enabled() {
newCtx = klog.NewContext(newCtx, klogger)
}
// The tracing package deals with both k8s trace and otel.
if span := tracing.SpanFromContext(refCtx); span != nil && *span != (tracing.Span{}) {
newCtx = tracing.ContextWithSpan(newCtx, span)
}
deadlineCancel := context.CancelFunc(func() {})
if deadline, ok := refCtx.Deadline(); ok {
newCtx, deadlineCancel = context.WithDeadline(newCtx, deadline)
}
newCtx, cancel := context.WithCancelCause(newCtx)
// We intentionally do not defer a cancel(nil) here. It wouldn't make sense to cancel until (*ResponseAdapter).Close() is called.
go func() { // Even context's own impls do goroutines for this type of pattern.
select {
case <-newCtx.Done():
// We don't have to do anything!
case <-refCtx.Done():
cancel(context.Cause(refCtx))
}
deadlineCancel()
}()
return newCtx, context.CancelFunc(func() {
cancel(nil)
deadlineCancel()
}), nil
}
// ResponseAdapter is an implementation of [http.ResponseWriter] that allows conversion to a [http.Response].
type ResponseAdapter struct {
req *http.Request
@@ -10,6 +10,8 @@ import (
"time"
"github.com/stretchr/testify/require"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/apiserver/pkg/endpoints/request"
grafanaresponsewriter "github.com/grafana/grafana/pkg/apiserver/endpoints/responsewriter"
)
@@ -158,6 +160,44 @@ func TestResponseAdapter(t *testing.T) {
}
wg.Wait()
})
t.Run("should fork the context", func(t *testing.T) {
t.Parallel()
type K int
var key K
baseCtx := context.Background()
baseCtx = context.WithValue(baseCtx, key, "hello, world!") // we expect this one not to be sent to the inner handler.
expectedUsr := &user.DefaultInfo{Name: "hello, world!"}
baseCtx = request.WithUser(baseCtx, expectedUsr)
// There are more keys to consider, but this should be sufficient to decide that we do actually propagate select data across.
client := &http.Client{
Transport: &roundTripperFunc{
ready: make(chan struct{}),
// ignore the lint error because the response is passed directly to the client,
// so the client will be responsible for closing the response body.
//nolint:bodyclose
fn: grafanaresponsewriter.WrapHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
require.Nil(t, r.Context().Value(key), "inner handler should not have a value for key of type K")
usr, ok := request.UserFrom(r.Context())
require.True(t, ok, "no user found in request context")
require.Equal(t, expectedUsr.Name, usr.GetName(), "user data was not propagated through request context")
_, err := w.Write([]byte("OK"))
require.NoError(t, err)
})),
},
}
req, err := http.NewRequestWithContext(baseCtx, http.MethodGet, "/", nil)
require.NoError(t, err)
resp, err := client.Do(req)
require.NoError(t, err, "request should not fail")
require.NoError(t, resp.Body.Close())
})
}
func syncHandler(w http.ResponseWriter, r *http.Request) {
+4 -1
View File
@@ -1,10 +1,13 @@
module github.com/grafana/grafana/pkg/apiserver
go 1.23.1
go 1.23.4
toolchain go1.23.6
require (
github.com/google/go-cmp v0.6.0
github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c
github.com/grafana/grafana-app-sdk/logging v0.30.0
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240701135906-559738ce6ae1
github.com/prometheus/client_golang v1.20.5
github.com/stretchr/testify v1.10.0
+2
View File
@@ -81,6 +81,8 @@ github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aN
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c h1:b0sPDtt33uFdmvUJjSCld3kwE2E49dUvevuUDSJsEuo=
github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c/go.mod h1:qYjSd1tmJiuVoSICp7Py9/zD54O9uQQA3wuM6Gg4DFM=
github.com/grafana/grafana-app-sdk/logging v0.30.0 h1:K/P/bm7Cp7Di4tqIJ3EQz2+842JozQGRaz62r95ApME=
github.com/grafana/grafana-app-sdk/logging v0.30.0/go.mod h1:xy6ZyVXl50Z3DBDLybvBPphbykPhuVNed/VNmen9DQM=
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240701135906-559738ce6ae1 h1:ItDcDxUjVLPKja+hogpqgW/kj8LxUL2qscelXIsN1Bs=
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240701135906-559738ce6ae1/go.mod h1:DkxMin+qOh1Fgkxfbt+CUfBqqsCQJMG9op8Os/irBPA=
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI=
+187 -135
View File
@@ -7,8 +7,8 @@ import (
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/api/meta"
metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -62,16 +62,15 @@ func TestMode1_Create(t *testing.T) {
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}
ls := legacyStoreMock{&mock.Mock{}, l}
us := storageMock{&mock.Mock{}, s}
if tt.setupLegacyFn != nil {
tt.setupLegacyFn(m, tt.input)
tt.setupLegacyFn(ls.Mock, tt.input)
}
if tt.setupStorageFn != nil {
tt.setupStorageFn(m)
tt.setupStorageFn(us.Mock)
}
dw := NewDualWriter(Mode1, ls, us, p, kind)
@@ -79,14 +78,14 @@ func TestMode1_Create(t *testing.T) {
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)
require.Error(t, err)
return
}
acc, err := meta.Accessor(obj)
assert.NoError(t, err)
assert.Equal(t, acc.GetResourceVersion(), "1")
assert.NotEqual(t, obj, anotherObj)
require.NoError(t, err)
require.Equal(t, acc.GetResourceVersion(), "1")
require.NotEqual(t, obj, anotherObj)
})
}
}
@@ -125,16 +124,15 @@ func TestMode1_CreateOnUnifiedStorage(t *testing.T) {
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}
ls := legacyStoreMock{&mock.Mock{}, l}
us := storageMock{&mock.Mock{}, s}
if tt.setupLegacyFn != nil {
tt.setupLegacyFn(m)
tt.setupLegacyFn(ls.Mock)
}
if tt.setupStorageFn != nil {
tt.setupStorageFn(m)
tt.setupStorageFn(us.Mock)
}
ctx := context.TODO()
@@ -144,7 +142,7 @@ func TestMode1_CreateOnUnifiedStorage(t *testing.T) {
dw := NewDualWriter(Mode1, ls, us, p, kind)
err := dw.(*DualWriterMode1).createOnUnifiedStorage(ctx, func(context.Context, runtime.Object) error { return nil }, tt.input, &metav1.CreateOptions{})
assert.NoError(t, err)
require.NoError(t, err)
})
}
}
@@ -154,14 +152,12 @@ func TestMode1_Get(t *testing.T) {
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",
name: "should succeed when getting an object from LegacyStorage",
setupLegacyFn: func(m *mock.Mock, name string) {
m.On("Get", mock.Anything, name, mock.Anything).Return(exampleObj, nil)
},
@@ -170,44 +166,56 @@ func TestMode1_Get(t *testing.T) {
},
},
{
name: "error when getting an object in the legacy store fails",
input: "object-fail",
name: "should error when getting an object from LegacyStorage fails",
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(exampleObj, nil)
},
wantErr: true,
},
{
name: "should not error when getting an object from UnifiedStorage fails",
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, errors.New("error"))
},
},
}
name := "foo"
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}
ls := legacyStoreMock{&mock.Mock{}, l}
us := storageMock{&mock.Mock{}, s}
if tt.setupLegacyFn != nil {
tt.setupLegacyFn(m, tt.input)
tt.setupLegacyFn(ls.Mock, name)
}
if tt.setupStorageFn != nil {
tt.setupStorageFn(m, tt.input)
tt.setupStorageFn(us.Mock, name)
}
dw := NewDualWriter(Mode1, ls, us, p, kind)
obj, err := dw.Get(context.Background(), tt.input, &metav1.GetOptions{})
obj, err := dw.Get(context.Background(), name, &metav1.GetOptions{})
if tt.wantErr {
assert.Error(t, err)
require.Error(t, err)
return
}
us.AssertNotCalled(t, "Get", context.Background(), tt.name, &metav1.GetOptions{})
assert.Equal(t, obj, exampleObj)
assert.NotEqual(t, obj, anotherObj)
require.Equal(t, obj, exampleObj)
require.NotEqual(t, obj, anotherObj)
})
}
}
@@ -221,41 +229,39 @@ func TestMode1_GetFromUnifiedStorage(t *testing.T) {
setupStorageFn func(m *mock.Mock, name string)
ctx *context.Context
name string
input string
}
tests :=
[]testCase{
{
name: "Get from unified storage",
input: "foo",
name: "should succeed when getting an object from UnifiedStorage",
setupStorageFn: func(m *mock.Mock, name string) {
m.On("Get", mock.Anything, name, mock.Anything).Return(exampleObj, nil)
},
},
{
name: "Get from unified storage works even if parent context is canceled",
input: "foo",
ctx: &ctxCanceled,
name: "should succeed when getting an object from UnifiedStorage even if parent context is canceled",
ctx: &ctxCanceled,
setupStorageFn: func(m *mock.Mock, name string) {
m.On("Get", mock.Anything, name, mock.Anything).Return(exampleObj, nil)
},
},
}
name := "foo"
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}
ls := legacyStoreMock{&mock.Mock{}, l}
us := storageMock{&mock.Mock{}, s}
if tt.setupLegacyFn != nil {
tt.setupLegacyFn(m, tt.input)
tt.setupLegacyFn(ls.Mock, name)
}
if tt.setupStorageFn != nil {
tt.setupStorageFn(m, tt.input)
tt.setupStorageFn(us.Mock, name)
}
ctx := context.TODO()
@@ -264,8 +270,8 @@ func TestMode1_GetFromUnifiedStorage(t *testing.T) {
}
dw := NewDualWriter(Mode1, ls, us, p, kind)
err := dw.(*DualWriterMode1).getFromUnifiedStorage(ctx, exampleObj, tt.input, &metav1.GetOptions{})
assert.NoError(t, err)
err := dw.(*DualWriterMode1).getFromUnifiedStorage(ctx, exampleObj, name, &metav1.GetOptions{})
require.NoError(t, err)
})
}
}
@@ -280,28 +286,39 @@ func TestMode1_List(t *testing.T) {
tests :=
[]testCase{
{
name: "error when listing an object in the legacy store is not implemented",
name: "should error when listing from LegacyStorage fails",
setupLegacyFn: func(m *mock.Mock) {
m.On("List", mock.Anything, mock.Anything).Return(&example.PodList{}, errors.New("error"))
m.On("List", mock.Anything, mock.Anything).Return(nil, errors.New("error"))
},
setupStorageFn: func(m *mock.Mock) {
m.On("List", mock.Anything, mock.Anything).Return(&example.PodList{}, nil)
},
wantErr: true,
},
{
name: "should not error when listing from UnifiedStorage fails",
setupLegacyFn: func(m *mock.Mock) {
m.On("List", mock.Anything, mock.Anything).Return(&example.PodList{}, nil)
},
setupStorageFn: func(m *mock.Mock) {
m.On("List", mock.Anything, mock.Anything).Return(nil, 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}
ls := legacyStoreMock{&mock.Mock{}, l}
us := storageMock{&mock.Mock{}, s}
if tt.setupLegacyFn != nil {
tt.setupLegacyFn(m)
tt.setupLegacyFn(ls.Mock)
}
if tt.setupStorageFn != nil {
tt.setupStorageFn(m)
tt.setupStorageFn(us.Mock)
}
dw := NewDualWriter(Mode1, ls, us, p, kind)
@@ -309,7 +326,7 @@ func TestMode1_List(t *testing.T) {
_, err := dw.List(context.Background(), &metainternalversion.ListOptions{})
if tt.wantErr {
assert.Error(t, err)
require.Error(t, err)
return
}
})
@@ -329,13 +346,16 @@ func TestMode1_ListFromUnifiedStorage(t *testing.T) {
tests :=
[]testCase{
{
name: "list from unified storage",
name: "should succeed when listing from UnifiedStorage",
setupStorageFn: func(m *mock.Mock) {
m.On("List", mock.Anything, mock.Anything).Return(anotherList, nil)
},
setupLegacyFn: func(m *mock.Mock) {
m.On("List", mock.Anything, mock.Anything).Return(anotherList, nil)
},
},
{
name: "list from unified storage works even if parent context is canceled",
name: "should succeed when listing from UnifiedStorage even if parent context is canceled",
ctx: &ctxCanceled,
setupStorageFn: func(m *mock.Mock) {
m.On("List", mock.Anything, mock.Anything).Return(anotherList, nil)
@@ -347,16 +367,15 @@ func TestMode1_ListFromUnifiedStorage(t *testing.T) {
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}
ls := legacyStoreMock{&mock.Mock{}, l}
us := storageMock{&mock.Mock{}, s}
if tt.setupLegacyFn != nil {
tt.setupLegacyFn(m)
tt.setupLegacyFn(ls.Mock)
}
if tt.setupStorageFn != nil {
tt.setupStorageFn(m)
tt.setupStorageFn(us.Mock)
}
ctx := context.TODO()
@@ -367,7 +386,7 @@ func TestMode1_ListFromUnifiedStorage(t *testing.T) {
dw := NewDualWriter(Mode1, ls, us, p, kind)
err := dw.(*DualWriterMode1).listFromUnifiedStorage(ctx, &metainternalversion.ListOptions{}, anotherList)
assert.NoError(t, err)
require.NoError(t, err)
})
}
}
@@ -377,56 +396,69 @@ func TestMode1_Delete(t *testing.T) {
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",
name: "should succeed when deleting an object from LegacyStorage",
setupLegacyFn: func(m *mock.Mock, name string) {
m.On("Delete", mock.Anything, name, mock.Anything, mock.Anything).Return(exampleObj, false, nil)
},
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 legacy store",
input: "object-fail",
name: "should error when deleting an object from LegacyStorage fails",
setupLegacyFn: func(m *mock.Mock, name string) {
m.On("Delete", mock.Anything, name, mock.Anything, mock.Anything).Return(nil, false, errors.New("error"))
},
setupStorageFn: func(m *mock.Mock, name string) {
m.On("Delete", mock.Anything, name, mock.Anything, mock.Anything).Return(exampleObj, false, nil)
},
wantErr: true,
},
{
name: "should not error when deleting an object from UnifiedStorage fails",
setupLegacyFn: func(m *mock.Mock, name string) {
m.On("Delete", mock.Anything, name, mock.Anything, mock.Anything).Return(exampleObj, false, nil)
},
setupStorageFn: func(m *mock.Mock, name string) {
m.On("Delete", mock.Anything, name, mock.Anything, mock.Anything).Return(nil, false, errors.New("error"))
},
},
}
name := "foo"
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}
ls := legacyStoreMock{&mock.Mock{}, l}
us := storageMock{&mock.Mock{}, s}
if tt.setupLegacyFn != nil {
tt.setupLegacyFn(m, tt.input)
tt.setupLegacyFn(ls.Mock, name)
}
if tt.setupStorageFn != nil {
tt.setupStorageFn(m, tt.input)
tt.setupStorageFn(us.Mock, name)
}
dw := NewDualWriter(Mode1, ls, us, p, kind)
obj, _, err := dw.Delete(context.Background(), tt.input, func(ctx context.Context, obj runtime.Object) error { return nil }, &metav1.DeleteOptions{})
obj, _, err := dw.Delete(context.Background(), name, func(ctx context.Context, obj runtime.Object) error { return nil }, &metav1.DeleteOptions{})
if tt.wantErr {
assert.Error(t, err)
require.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)
us.AssertNotCalled(t, "Delete", context.Background(), name, func(ctx context.Context, obj runtime.Object) error { return nil }, &metav1.DeleteOptions{})
require.Equal(t, obj, exampleObj)
require.NotEqual(t, obj, anotherObj)
})
}
}
@@ -440,39 +472,39 @@ func TestMode1_DeleteFromUnifiedStorage(t *testing.T) {
setupLegacyFn func(m *mock.Mock, name string)
setupStorageFn func(m *mock.Mock, name string)
name string
input string
}
tests :=
[]testCase{
{
name: "Delete from unified storage",
setupStorageFn: func(m *mock.Mock, input string) {
m.On("Delete", mock.Anything, input, mock.Anything, mock.Anything).Return(exampleObj, false, nil)
name: "should succeed when deleting an object from UnifiedStorage",
setupStorageFn: func(m *mock.Mock, name string) {
m.On("Delete", mock.Anything, name, mock.Anything, mock.Anything).Return(exampleObj, false, nil)
},
},
{
name: "Delete from unified storage works even if parent context is canceled",
name: "should succeed when deleting an object from UnifiedStorage even if parent context is canceled",
ctx: &ctxCanceled,
setupStorageFn: 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, name string) {
m.On("Delete", mock.Anything, name, mock.Anything, mock.Anything).Return(exampleObj, false, nil)
},
},
}
name := "foo"
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}
ls := legacyStoreMock{&mock.Mock{}, l}
us := storageMock{&mock.Mock{}, s}
if tt.setupLegacyFn != nil {
tt.setupLegacyFn(m, tt.input)
tt.setupLegacyFn(ls.Mock, name)
}
if tt.setupStorageFn != nil {
tt.setupStorageFn(m, tt.input)
tt.setupStorageFn(us.Mock, name)
}
ctx := context.TODO()
@@ -482,8 +514,8 @@ func TestMode1_DeleteFromUnifiedStorage(t *testing.T) {
dw := NewDualWriter(Mode1, ls, us, p, kind)
err := dw.(*DualWriterMode1).deleteFromUnifiedStorage(ctx, exampleObj, tt.input, func(ctx context.Context, obj runtime.Object) error { return nil }, &metav1.DeleteOptions{})
assert.NoError(t, err)
err := dw.(*DualWriterMode1).deleteFromUnifiedStorage(ctx, exampleObj, name, func(ctx context.Context, obj runtime.Object) error { return nil }, &metav1.DeleteOptions{})
require.NoError(t, err)
})
}
}
@@ -499,36 +531,51 @@ func TestMode1_DeleteCollection(t *testing.T) {
tests :=
[]testCase{
{
name: "deleting a collection in the legacy store",
name: "should succeed when deleting a collection from LegacyStorage",
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)
},
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 legacy store",
name: "should error when deleting a collection from LegacyStorage fails",
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"))
},
setupStorageFn: func(m *mock.Mock, input *metav1.DeleteOptions) {
m.On("DeleteCollection", mock.Anything, mock.Anything, input, mock.Anything).Return(exampleObj, nil)
},
wantErr: true,
},
{
name: "should not error when deleting a collection from UnifiedStorage fails",
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)
},
setupStorageFn: func(m *mock.Mock, input *metav1.DeleteOptions) {
m.On("DeleteCollection", mock.Anything, mock.Anything, input, mock.Anything).Return(nil, errors.New("error"))
},
},
}
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}
ls := legacyStoreMock{&mock.Mock{}, l}
us := storageMock{&mock.Mock{}, s}
if tt.setupLegacyFn != nil {
tt.setupLegacyFn(m, tt.input)
tt.setupLegacyFn(ls.Mock, tt.input)
}
if tt.setupStorageFn != nil {
tt.setupStorageFn(m, tt.input)
tt.setupStorageFn(us.Mock, tt.input)
}
dw := NewDualWriter(Mode1, ls, us, p, kind)
@@ -536,13 +583,13 @@ func TestMode1_DeleteCollection(t *testing.T) {
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)
require.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)
require.Equal(t, obj, exampleObj)
require.NotEqual(t, obj, anotherObj)
})
}
}
@@ -561,14 +608,14 @@ func TestMode1_DeleteCollectionFromUnifiedStorage(t *testing.T) {
tests :=
[]testCase{
{
name: "Delete Collection from unified storage",
name: "should succeed when deleting a collection from UnifiedStorage",
input: &metav1.DeleteOptions{TypeMeta: metav1.TypeMeta{Kind: "foo"}},
setupStorageFn: func(m *mock.Mock) {
m.On("DeleteCollection", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(exampleObj, nil)
},
},
{
name: "Delete Collection from unified storage works even if parent context is canceled",
name: "should succeed when deleting a collection from UnifiedStorage even if parent context is canceled",
input: &metav1.DeleteOptions{TypeMeta: metav1.TypeMeta{Kind: "foo"}},
ctx: &ctxCanceled,
setupStorageFn: func(m *mock.Mock) {
@@ -581,16 +628,15 @@ func TestMode1_DeleteCollectionFromUnifiedStorage(t *testing.T) {
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}
ls := legacyStoreMock{&mock.Mock{}, l}
us := storageMock{&mock.Mock{}, s}
if tt.setupLegacyFn != nil {
tt.setupLegacyFn(m)
tt.setupLegacyFn(ls.Mock)
}
if tt.setupStorageFn != nil {
tt.setupStorageFn(m)
tt.setupStorageFn(us.Mock)
}
ctx := context.TODO()
@@ -601,7 +647,7 @@ func TestMode1_DeleteCollectionFromUnifiedStorage(t *testing.T) {
dw := NewDualWriter(Mode1, ls, us, p, kind)
err := dw.(*DualWriterMode1).deleteCollectionFromUnifiedStorage(ctx, exampleObj, func(ctx context.Context, obj runtime.Object) error { return nil }, tt.input, &metainternalversion.ListOptions{})
assert.NoError(t, err)
require.NoError(t, err)
})
}
}
@@ -611,14 +657,12 @@ func TestMode1_Update(t *testing.T) {
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 legacy",
input: "foo",
name: "should succeed when updating an object in LegacyStorage",
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)
},
@@ -627,8 +671,7 @@ func TestMode1_Update(t *testing.T) {
},
},
{
name: "error updating an object in legacy",
input: "object-fail",
name: "should error when updating an object in LegacyStorage fails",
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"))
},
@@ -637,35 +680,45 @@ func TestMode1_Update(t *testing.T) {
},
wantErr: true,
},
{
name: "should not error when updating an object in UnifiedStorage fails",
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(nil, false, errors.New("error"))
},
},
}
name := "foo"
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}
ls := legacyStoreMock{&mock.Mock{}, l}
us := storageMock{&mock.Mock{}, s}
if tt.setupLegacyFn != nil {
tt.setupLegacyFn(m, tt.input)
tt.setupLegacyFn(ls.Mock, name)
}
if tt.setupStorageFn != nil {
tt.setupStorageFn(m, tt.input)
tt.setupStorageFn(us.Mock, name)
}
dw := NewDualWriter(Mode1, 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{})
obj, _, err := dw.Update(context.Background(), name, 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)
require.Error(t, err)
return
}
assert.Equal(t, obj, exampleObj)
assert.NotEqual(t, obj, anotherObj)
require.Equal(t, obj, exampleObj)
require.NotEqual(t, obj, anotherObj)
})
}
}
@@ -680,13 +733,11 @@ func TestMode1_UpdateOnUnifiedStorage(t *testing.T) {
setupStorageFn func(m *mock.Mock, input string)
setupGetFn func(m *mock.Mock, input string)
name string
input string
}
tests :=
[]testCase{
{
name: "Update on unified storage",
input: "foo",
name: "should succeed when updating an object on UnifiedStorage",
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)
},
@@ -695,9 +746,8 @@ func TestMode1_UpdateOnUnifiedStorage(t *testing.T) {
},
},
{
name: "Update on unified storage works even if parent context is canceled",
ctx: &ctxCanceled,
input: "foo",
name: "should succeed when updating an object on UnifiedStorage even if parent context is canceled",
ctx: &ctxCanceled,
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)
},
@@ -707,24 +757,26 @@ func TestMode1_UpdateOnUnifiedStorage(t *testing.T) {
},
}
name := "foo"
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}
ls := legacyStoreMock{&mock.Mock{}, l}
us := storageMock{&mock.Mock{}, s}
if tt.setupLegacyFn != nil {
tt.setupLegacyFn(m, tt.input)
tt.setupLegacyFn(ls.Mock, name)
}
if tt.setupStorageFn != nil {
tt.setupStorageFn(m, tt.input)
tt.setupStorageFn(us.Mock, name)
}
if tt.setupGetFn != nil {
tt.setupGetFn(m, tt.input)
tt.setupGetFn(ls.Mock, name)
tt.setupGetFn(us.Mock, name)
}
ctx := context.TODO()
@@ -734,8 +786,8 @@ func TestMode1_UpdateOnUnifiedStorage(t *testing.T) {
dw := NewDualWriter(Mode1, ls, us, p, kind)
err := dw.(*DualWriterMode1).updateOnUnifiedStorageMode1(ctx, exampleObj, 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{})
assert.NoError(t, err)
err := dw.(*DualWriterMode1).updateOnUnifiedStorageMode1(ctx, exampleObj, name, 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{})
require.NoError(t, err)
})
}
}
+234 -184
View File
@@ -29,21 +29,25 @@ func TestMode2_Create(t *testing.T) {
tests :=
[]testCase{
{
name: "creating an object in both the LegacyStorage and Storage",
name: "should create 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)
m.On("Create", mock.Anything, input, 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)
setupStorageFn: func(m *mock.Mock, _ runtime.Object) {
// We don't use the input here, as the input is transformed before being passed to unified storage.
m.On("Create", mock.Anything, exampleObjNoRV, mock.Anything, mock.Anything).Return(exampleObj, nil)
},
},
{
name: "error when creating object in the legacy store fails",
name: "should return an error when creating an object in the LegacyStorage 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"))
},
setupStorageFn: func(m *mock.Mock, input runtime.Object) {
m.On("Create", mock.Anything, input, mock.Anything, mock.Anything).Return(exampleObj, nil)
},
wantErr: true,
},
}
@@ -52,16 +56,15 @@ func TestMode2_Create(t *testing.T) {
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}
ls := legacyStoreMock{&mock.Mock{}, l}
us := storageMock{&mock.Mock{}, s}
if tt.setupLegacyFn != nil {
tt.setupLegacyFn(m, tt.input)
tt.setupLegacyFn(ls.Mock, tt.input)
}
if tt.setupStorageFn != nil {
tt.setupStorageFn(m, tt.input)
tt.setupStorageFn(us.Mock, tt.input)
}
dw := NewDualWriter(Mode2, ls, us, p, kind)
@@ -89,7 +92,7 @@ func TestMode2_Get(t *testing.T) {
tests :=
[]testCase{
{
name: "getting an object from storage",
name: "should get an object from both the LegacyStorage and Storage",
input: "foo",
setupLegacyFn: func(m *mock.Mock, input string) {
m.On("Get", mock.Anything, input, mock.Anything).Return(exampleObj, nil)
@@ -99,7 +102,7 @@ func TestMode2_Get(t *testing.T) {
},
},
{
name: "object not present in storage but present in legacy store",
name: "should return an error when getting an object from the Storage fails",
input: "foo",
setupLegacyFn: func(m *mock.Mock, input string) {
m.On("Get", mock.Anything, input, mock.Anything).Return(exampleObj, nil)
@@ -107,9 +110,22 @@ func TestMode2_Get(t *testing.T) {
setupStorageFn: func(m *mock.Mock, input string) {
m.On("Get", mock.Anything, input, mock.Anything).Return(nil, errors.New("error"))
},
wantErr: true,
},
{
name: "error when getting object in both stores fails",
name: "should not error when object is not found in the Storage but found in the LegacyStorage",
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, apierrors.NewNotFound(
schema.GroupResource{Group: "", Resource: "pods"}, "not-found"))
},
wantErr: true,
},
{
name: "should return an error when getting an object from both the LegacyStorage and Storage fails",
input: "object-fail",
setupLegacyFn: func(m *mock.Mock, input string) {
m.On("Get", mock.Anything, input, mock.Anything).Return(nil, errors.New("error"))
@@ -125,16 +141,15 @@ func TestMode2_Get(t *testing.T) {
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}
ls := legacyStoreMock{&mock.Mock{}, l}
us := storageMock{&mock.Mock{}, s}
if tt.setupLegacyFn != nil {
tt.setupLegacyFn(m, tt.input)
tt.setupLegacyFn(ls.Mock, tt.input)
}
if tt.setupStorageFn != nil {
tt.setupStorageFn(m, tt.input)
tt.setupStorageFn(us.Mock, tt.input)
}
dw := NewDualWriter(Mode2, ls, us, p, kind)
@@ -163,7 +178,7 @@ func TestMode2_List(t *testing.T) {
tests :=
[]testCase{
{
name: "object present in both Storage and LegacyStorage",
name: "should return a list of objects from both the LegacyStorage and Storage",
inputLegacy: exampleOption,
setupLegacyFn: func(m *mock.Mock) {
m.On("List", mock.Anything, mock.Anything).Return(exampleList, nil)
@@ -172,168 +187,25 @@ func TestMode2_List(t *testing.T) {
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 {
require.Error(t, err)
return
}
require.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 {
require.Error(t, err)
return
}
require.Equal(t, obj, exampleObj)
require.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",
name: "should return an error when listing objects from the LegacyStorage fails",
inputLegacy: exampleOption,
setupLegacyFn: func(m *mock.Mock) {
m.On("DeleteCollection", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(exampleList, nil)
m.On("List", mock.Anything, mock.Anything).Return(nil, errors.New("error"))
},
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: "fail",
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"))
m.On("List", mock.Anything, mock.Anything).Return(anotherList, nil)
},
wantErr: true,
},
{
name: "deleting a collection when error in legacy store",
input: "fail",
name: "should return an error when listing objects from the Storage fails",
inputLegacy: exampleOption,
setupLegacyFn: func(m *mock.Mock) {
m.On("DeleteCollection", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, errors.New("error"))
m.On("List", mock.Anything, mock.Anything).Return(exampleList, nil)
},
setupStorageFn: func(m *mock.Mock) {
m.On("List", mock.Anything, mock.Anything).Return(nil, errors.New("error"))
},
wantErr: true,
},
@@ -356,7 +228,177 @@ func TestMode2_DeleteCollection(t *testing.T) {
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{})
obj, err := dw.List(context.Background(), &metainternalversion.ListOptions{})
if tt.wantErr {
require.Error(t, err)
return
}
require.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
wantErr bool
}
tests :=
[]testCase{
{
name: "should delete an object from both the LegacyStorage and Storage",
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: "should return an error when deleting an object from the LegacyStorage fails",
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(exampleObj, false, nil)
},
wantErr: true,
},
{
name: "should return an error when deleting an object from the Storage fails",
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(nil, false, errors.New("error"))
},
wantErr: true,
},
{
name: "should return an error when the object is not found in LegacyStorage",
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"}, input))
},
setupStorageFn: func(m *mock.Mock, input string) {
m.On("Delete", mock.Anything, input, mock.Anything, mock.Anything).Return(exampleObj, false, nil)
},
wantErr: true,
},
{
name: "should not return an error when the object is not found in Storage",
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(nil, false,
apierrors.NewNotFound(schema.GroupResource{Group: "", Resource: "pods"}, input))
},
},
{
name: "should return an error when deleting an object from both the LegacyStorage and Storage fails",
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,
},
}
name := "foo"
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
l := (LegacyStorage)(nil)
s := (Storage)(nil)
ls := legacyStoreMock{&mock.Mock{}, l}
us := storageMock{&mock.Mock{}, s}
if tt.setupLegacyFn != nil {
tt.setupLegacyFn(ls.Mock, name)
}
if tt.setupStorageFn != nil {
tt.setupStorageFn(us.Mock, name)
}
dw := NewDualWriter(Mode2, ls, us, p, kind)
obj, _, err := dw.Delete(context.Background(), name, func(context.Context, runtime.Object) error { return nil }, &metav1.DeleteOptions{})
if tt.wantErr {
require.Error(t, err)
return
}
require.Equal(t, obj, exampleObj)
require.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
wantErr bool
}
tests :=
[]testCase{
{
name: "should delete a collection from both the LegacyStorage and Storage",
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: "should return an error when deleting a collection from the Storage fails",
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(nil, errors.New("error"))
},
wantErr: true,
},
{
name: "should return an error when deleting a collection from the LegacyStorage fails",
setupLegacyFn: func(m *mock.Mock) {
m.On("DeleteCollection", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, errors.New("error"))
},
wantErr: true,
},
}
name := "foo"
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
l := (LegacyStorage)(nil)
s := (Storage)(nil)
ls := legacyStoreMock{&mock.Mock{}, l}
us := storageMock{&mock.Mock{}, s}
if tt.setupLegacyFn != nil {
tt.setupLegacyFn(ls.Mock)
}
if tt.setupStorageFn != nil {
tt.setupStorageFn(us.Mock)
}
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: name}}, &metainternalversion.ListOptions{})
if tt.wantErr {
require.Error(t, err)
@@ -373,14 +415,12 @@ func TestMode2_Update(t *testing.T) {
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",
name: "should succeed when updating an object in both the LegacyStorage and Storage is successful",
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)
},
@@ -390,34 +430,44 @@ func TestMode2_Update(t *testing.T) {
expectedObj: exampleObj,
},
{
name: "error updating legacy store",
input: "object-fail",
name: "should return an error when updating the LegacyStorage fails",
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,
},
{
name: "should return an error when updating the Storage fails",
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(nil, false, errors.New("error"))
},
wantErr: true,
},
}
name := "foo"
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}
ls := legacyStoreMock{&mock.Mock{}, l}
us := storageMock{&mock.Mock{}, s}
if tt.setupLegacyFn != nil {
tt.setupLegacyFn(m, tt.input)
tt.setupLegacyFn(ls.Mock, name)
}
if tt.setupStorageFn != nil {
tt.setupStorageFn(m, tt.input)
tt.setupStorageFn(us.Mock, name)
}
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{})
obj, _, err := dw.Update(context.Background(), name, 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 {
require.Error(t, err)
+111 -120
View File
@@ -6,8 +6,8 @@ import (
"testing"
"github.com/prometheus/client_golang/prometheus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -26,17 +26,18 @@ func TestMode3_Create(t *testing.T) {
tests :=
[]testCase{
{
name: "creating an object in both the LegacyStorage and Storage",
name: "should succeed when 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).Once()
m.On("Create", mock.Anything, input, mock.Anything, mock.Anything).Return(exampleObj, nil).Once()
},
setupStorageFn: func(m *mock.Mock, input runtime.Object) {
m.On("Create", mock.Anything, exampleObj, mock.Anything, mock.Anything).Return(exampleObj, nil).Once()
setupStorageFn: func(m *mock.Mock, _ runtime.Object) {
// We don't use the input here, as the input is transformed before being passed to unified storage.
m.On("Create", mock.Anything, exampleObjNoRV, mock.Anything, mock.Anything).Return(exampleObj, nil).Once()
},
},
{
name: "error when creating object in the legacy store fails",
name: "should return an error when creating an 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")).Once()
@@ -44,14 +45,15 @@ func TestMode3_Create(t *testing.T) {
wantErr: true,
},
{
name: "error when creating object in the unistore fails - legacy delete should be called",
name: "should return an error when creating an object in the unified store fails and delete from LegacyStorage",
input: exampleObj,
setupLegacyFn: func(m *mock.Mock, input runtime.Object) {
m.On("Delete", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(exampleObj, true, nil).Once()
m.On("Create", mock.Anything, exampleObjNoRV, mock.Anything, mock.Anything).Return(exampleObj, nil).Once()
m.On("Create", mock.Anything, input, mock.Anything, mock.Anything).Return(exampleObj, nil).Once()
},
setupStorageFn: func(m *mock.Mock, input runtime.Object) {
m.On("Create", mock.Anything, exampleObj, mock.Anything, mock.Anything).Return(exampleObj, errors.New("error")).Once()
setupStorageFn: func(m *mock.Mock, _ runtime.Object) {
// We don't use the input here, as the input is transformed before being passed to unified storage.
m.On("Create", mock.Anything, exampleObjNoRV, mock.Anything, mock.Anything).Return(nil, errors.New("error")).Once()
},
wantErr: true,
},
@@ -61,16 +63,15 @@ func TestMode3_Create(t *testing.T) {
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}
ls := legacyStoreMock{&mock.Mock{}, l}
us := storageMock{&mock.Mock{}, s}
if tt.setupLegacyFn != nil {
tt.setupLegacyFn(m, tt.input)
tt.setupLegacyFn(ls.Mock, tt.input)
}
if tt.setupStorageFn != nil {
tt.setupStorageFn(m, tt.input)
tt.setupStorageFn(us.Mock, tt.input)
}
dw := NewDualWriter(Mode3, ls, us, p, kind)
@@ -78,66 +79,83 @@ func TestMode3_Create(t *testing.T) {
obj, err := dw.Create(context.Background(), tt.input, createFn, &metav1.CreateOptions{})
if tt.wantErr {
assert.Error(t, err)
require.Error(t, err)
return
}
assert.Equal(t, exampleObj, obj)
require.Equal(t, exampleObj, obj)
})
}
}
func TestMode3_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 unified store",
input: "foo",
name: "should succeed when getting an object from both stores",
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(exampleObj, nil)
},
},
{
name: "error when getting an object in the unified store fails",
input: "object-fail",
name: "should return an error when getting an object in the unified store fails",
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, errors.New("error"))
},
wantErr: true,
},
{
name: "should succeed when getting an object in the LegacyStorage fails",
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(exampleObj, nil)
},
},
}
name := "foo"
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}
ls := legacyStoreMock{&mock.Mock{}, l}
us := storageMock{&mock.Mock{}, s}
if tt.setupLegacyFn != nil {
tt.setupLegacyFn(ls.Mock, name)
}
if tt.setupStorageFn != nil {
tt.setupStorageFn(m, tt.input)
tt.setupStorageFn(us.Mock, name)
}
p := prometheus.NewRegistry()
dw := NewDualWriter(Mode3, ls, us, p, kind)
obj, err := dw.Get(context.Background(), tt.input, &metav1.GetOptions{})
obj, err := dw.Get(context.Background(), name, &metav1.GetOptions{})
if tt.wantErr {
assert.Error(t, err)
require.Error(t, err)
return
}
assert.Equal(t, obj, exampleObj)
assert.NotEqual(t, obj, anotherObj)
require.Equal(t, obj, exampleObj)
require.NotEqual(t, obj, anotherObj)
})
}
}
@@ -150,38 +168,36 @@ func TestMode1_GetFromLegacyStorage(t *testing.T) {
setupLegacyFn func(m *mock.Mock, name string)
ctx *context.Context
name string
input string
}
tests :=
[]testCase{
{
name: "Get from legacy storage",
input: "foo",
name: "should succeed when getting an object from the LegacyStorage",
setupLegacyFn: func(m *mock.Mock, name string) {
m.On("Get", mock.Anything, name, mock.Anything).Return(exampleObj, nil)
},
},
{
name: "Get from legacy storage works even if parent context is canceled",
input: "foo",
ctx: &ctxCanceled,
name: "should succeed when getting an object from the LegacyStorage even if parent context is canceled",
ctx: &ctxCanceled,
setupLegacyFn: func(m *mock.Mock, name string) {
m.On("Get", mock.Anything, name, mock.Anything).Return(exampleObj, nil)
},
},
}
name := "foo"
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}
ls := legacyStoreMock{&mock.Mock{}, l}
us := storageMock{&mock.Mock{}, s}
if tt.setupLegacyFn != nil {
tt.setupLegacyFn(m, tt.input)
tt.setupLegacyFn(ls.Mock, name)
}
ctx := context.TODO()
@@ -190,8 +206,8 @@ func TestMode1_GetFromLegacyStorage(t *testing.T) {
}
dw := NewDualWriter(Mode3, ls, us, p, kind)
err := dw.(*DualWriterMode3).getFromLegacyStorage(ctx, exampleObj, tt.input, &metav1.GetOptions{})
assert.NoError(t, err)
err := dw.(*DualWriterMode3).getFromLegacyStorage(ctx, exampleObj, name, &metav1.GetOptions{})
require.NoError(t, err)
})
}
}
@@ -200,22 +216,19 @@ 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"}},
name: "should return an error when listing an object in the UnifiedStorage is failing",
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"}},
name: "should succeed when listing objects in the UnifiedStorage is successful",
setupStorageFn: func(m *mock.Mock, options *metainternalversion.ListOptions) {
m.On("List", mock.Anything, options).Return(exampleList, nil)
},
@@ -226,26 +239,25 @@ func TestMode3_List(t *testing.T) {
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}
ls := legacyStoreMock{&mock.Mock{}, l}
us := storageMock{&mock.Mock{}, s}
if tt.setupStorageFn != nil {
tt.setupStorageFn(m, tt.options)
tt.setupStorageFn(us.Mock, &metainternalversion.ListOptions{TypeMeta: metav1.TypeMeta{Kind: "foo"}})
}
dw := NewDualWriter(Mode3, ls, us, p, kind)
res, err := dw.List(context.Background(), tt.options)
res, err := dw.List(context.Background(), &metainternalversion.ListOptions{TypeMeta: metav1.TypeMeta{Kind: "foo"}})
if tt.wantErr {
assert.Error(t, err)
require.Error(t, err)
return
}
assert.Equal(t, exampleList, res)
assert.NotEqual(t, anotherList, res)
require.Equal(t, exampleList, res)
require.NotEqual(t, anotherList, res)
})
}
}
@@ -255,14 +267,12 @@ func TestMode3_Delete(t *testing.T) {
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",
name: "should succeed when deleting an object in both stores",
setupLegacyFn: func(m *mock.Mock, input string) {
m.On("Delete", mock.Anything, input, mock.Anything, mock.Anything).Return(exampleObj, false, nil)
},
@@ -271,39 +281,25 @@ func TestMode3_Delete(t *testing.T) {
},
},
{
name: "object delete in legacy not found, but found in storage",
input: "foo",
name: "should succeed when deleting an object in the LegacyStorage is not found, but found in the UnifiedStorage",
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"))
m.On("Delete", mock.Anything, input, mock.Anything, mock.Anything).Return(nil, false, apierrors.NewNotFound(schema.GroupResource{Group: "", Resource: "pods"}, input))
},
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",
name: "should succeed when deleting an object in the UnifiedStorage is not found in the LegacyStorage",
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"}, input))
},
setupStorageFn: 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",
name: "should return an error when deleting an object in the LegacyStorage and UnifiedStorage is failing",
setupLegacyFn: func(m *mock.Mock, input string) {
m.On("Delete", mock.Anything, input, mock.Anything, mock.Anything).Return(nil, false, errors.New("error"))
},
@@ -314,33 +310,34 @@ func TestMode3_Delete(t *testing.T) {
},
}
name := "foo"
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}
ls := legacyStoreMock{&mock.Mock{}, l}
us := storageMock{&mock.Mock{}, s}
if tt.setupLegacyFn != nil {
tt.setupLegacyFn(m, tt.input)
tt.setupLegacyFn(ls.Mock, name)
}
if tt.setupStorageFn != nil {
tt.setupStorageFn(m, tt.input)
tt.setupStorageFn(us.Mock, name)
}
dw := NewDualWriter(Mode3, ls, us, p, kind)
obj, _, err := dw.Delete(context.Background(), tt.input, func(context.Context, runtime.Object) error { return nil }, &metav1.DeleteOptions{})
obj, _, err := dw.Delete(context.Background(), name, func(context.Context, runtime.Object) error { return nil }, &metav1.DeleteOptions{})
if tt.wantErr {
assert.Error(t, err)
require.Error(t, err)
return
}
assert.Equal(t, obj, exampleObj)
assert.NotEqual(t, obj, anotherObj)
require.Equal(t, obj, exampleObj)
require.NotEqual(t, obj, anotherObj)
})
}
}
@@ -350,14 +347,12 @@ func TestMode3_DeleteCollection(t *testing.T) {
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",
name: "should succeed when deleting a collection in both stores",
setupLegacyFn: func(m *mock.Mock) {
m.On("DeleteCollection", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(exampleList, nil)
},
@@ -366,8 +361,7 @@ func TestMode3_DeleteCollection(t *testing.T) {
},
},
{
name: "error deleting a collection in the storage when legacy store is successful",
input: "foo",
name: "should return an error when deleting a collection in the storage fails and LegacyStorage is successful",
setupLegacyFn: func(m *mock.Mock) {
m.On("DeleteCollection", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(exampleObj, nil)
},
@@ -377,40 +371,40 @@ func TestMode3_DeleteCollection(t *testing.T) {
wantErr: true,
},
{
name: "error deleting a collection legacy store",
input: "fail",
name: "should return an error when deleting a collection in the LegacyStorage fails",
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"))
m.On("DeleteCollection", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, errors.New("error"))
},
wantErr: true,
},
}
name := "foo"
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}
ls := legacyStoreMock{&mock.Mock{}, l}
us := storageMock{&mock.Mock{}, s}
if tt.setupLegacyFn != nil {
tt.setupLegacyFn(m)
tt.setupLegacyFn(ls.Mock)
}
if tt.setupStorageFn != nil {
tt.setupStorageFn(m)
tt.setupStorageFn(us.Mock)
}
dw := NewDualWriter(Mode3, 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{})
obj, err := dw.DeleteCollection(context.Background(), func(ctx context.Context, obj runtime.Object) error { return nil }, &metav1.DeleteOptions{TypeMeta: metav1.TypeMeta{Kind: name}}, &metainternalversion.ListOptions{})
if tt.wantErr {
assert.Error(t, err)
require.Error(t, err)
return
}
assert.Equal(t, exampleList, obj)
require.Equal(t, exampleList, obj)
})
}
}
@@ -421,14 +415,12 @@ func TestMode3_Update(t *testing.T) {
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",
name: "should succeed when updating an object in both stores",
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).Once()
},
@@ -438,16 +430,14 @@ func TestMode3_Update(t *testing.T) {
expectedObj: exampleObj,
},
{
name: "error updating legacy store",
input: "object-fail",
name: "should return an error when updating an object in the LegacyStorage fails",
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")).Once()
},
wantErr: true,
},
{
name: "error updating unistore",
input: "object-fail",
name: "should return an error when updating an object in the UnifiedStorage fails",
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).Once()
},
@@ -458,33 +448,34 @@ func TestMode3_Update(t *testing.T) {
},
}
name := "foo"
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}
ls := legacyStoreMock{&mock.Mock{}, l}
us := storageMock{&mock.Mock{}, s}
if tt.setupLegacyFn != nil {
tt.setupLegacyFn(m, tt.input)
tt.setupLegacyFn(ls.Mock, name)
}
if tt.setupStorageFn != nil {
tt.setupStorageFn(m, tt.input)
tt.setupStorageFn(us.Mock, name)
}
dw := NewDualWriter(Mode3, 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{})
obj, _, err := dw.Update(context.Background(), name, 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)
require.Error(t, err)
return
}
assert.Equal(t, tt.expectedObj, obj)
assert.NotEqual(t, anotherObj, obj)
require.Equal(t, tt.expectedObj, obj)
require.NotEqual(t, anotherObj, obj)
})
}
}
+12 -9
View File
@@ -5,8 +5,8 @@ import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/apis/example"
@@ -57,13 +57,16 @@ func TestSetDualWritingMode(t *testing.T) {
for _, tt := range tests {
l := (LegacyStorage)(nil)
s := (Storage)(nil)
m := &mock.Mock{}
m.On("List", mock.Anything, mock.Anything).Return(exampleList, nil)
m.On("List", mock.Anything, mock.Anything).Return(anotherList, nil)
sm := &mock.Mock{}
sm.On("List", mock.Anything, mock.Anything).Return(anotherList, nil)
sm.On("Update", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(exampleObj, false, nil)
sm.On("Delete", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(exampleObj, false, nil)
us := storageMock{sm, s}
ls := legacyStoreMock{m, l}
us := storageMock{m, s}
lm := &mock.Mock{}
lm.On("List", mock.Anything, mock.Anything).Return(exampleList, nil)
ls := legacyStoreMock{lm, l}
dwMode, err := SetDualWritingMode(context.Background(), tt.kvStore, &SyncerConfig{
LegacyStorage: ls,
@@ -77,8 +80,8 @@ func TestSetDualWritingMode(t *testing.T) {
DataSyncerRecordsLimit: 1000,
DataSyncerInterval: time.Hour,
})
assert.NoError(t, err)
assert.Equal(t, tt.expectedMode, dwMode)
require.NoError(t, err)
require.Equal(t, tt.expectedMode, dwMode)
}
}
@@ -121,7 +124,7 @@ func TestCompare(t *testing.T) {
}
for _, tt := range testCase {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.expected, Compare(tt.input1, tt.input2))
require.Equal(t, tt.expected, Compare(tt.input1, tt.input2))
})
}
}
+24 -41
View File
@@ -5,7 +5,6 @@ import (
"errors"
"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"
@@ -30,8 +29,8 @@ func (m legacyStoreMock) Get(ctx context.Context, name string, options *metav1.G
}
args := m.Called(ctx, name, options)
if name == "object-fail" {
return nil, args.Error(1)
if err := args.Get(1); err != nil {
return nil, err.(error)
}
return args.Get(0).(runtime.Object), args.Error(1)
}
@@ -44,13 +43,8 @@ func (m legacyStoreMock) Create(ctx context.Context, obj runtime.Object, createV
}
args := m.Called(ctx, obj, createValidation, options)
acc, err := meta.Accessor(obj)
if err != nil {
return nil, args.Error(1)
}
name := acc.GetName()
if name == "object-fail" {
return nil, args.Error(1)
if err := args.Get(1); err != nil {
return nil, err.(error)
}
return args.Get(0).(runtime.Object), args.Error(1)
}
@@ -63,8 +57,8 @@ func (m legacyStoreMock) List(ctx context.Context, options *metainternalversion.
}
args := m.Called(ctx, options)
if options.Kind == "fail" {
return nil, args.Error(1)
if err := args.Get(1); err != nil {
return nil, err.(error)
}
return args.Get(0).(runtime.Object), args.Error(1)
}
@@ -80,8 +74,8 @@ func (m legacyStoreMock) Update(ctx context.Context, name string, objInfo rest.U
default:
}
args := m.Called(ctx, name, objInfo, createValidation, updateValidation, forceAllowCreate, options)
if name == "object-fail" {
return nil, false, args.Error(2)
if err := args.Get(2); err != nil {
return nil, false, err.(error)
}
return args.Get(0).(runtime.Object), args.Bool(1), args.Error(2)
}
@@ -94,11 +88,8 @@ func (m legacyStoreMock) Delete(ctx context.Context, name string, deleteValidati
}
args := m.Called(ctx, name, deleteValidation, options)
if name == "object-fail" {
return nil, false, args.Error(2)
}
if name == "not-found-legacy" {
return nil, false, args.Error(2)
if err := args.Get(2); err != nil {
return nil, false, err.(error)
}
return args.Get(0).(runtime.Object), args.Bool(1), args.Error(2)
}
@@ -110,8 +101,8 @@ func (m legacyStoreMock) DeleteCollection(ctx context.Context, deleteValidation
default:
}
args := m.Called(ctx, deleteValidation, options, listOptions)
if options.Kind == "fail" {
return nil, args.Error(1)
if err := args.Get(1); err != nil {
return nil, err.(error)
}
return args.Get(0).(runtime.Object), args.Error(1)
}
@@ -125,11 +116,8 @@ func (m storageMock) Get(ctx context.Context, name string, options *metav1.GetOp
}
args := m.Called(ctx, name, options)
if name == "object-fail" {
return nil, args.Error(1)
}
if name == "not-found" {
return nil, args.Error(1)
if err := args.Get(1); err != nil {
return nil, err.(error)
}
return args.Get(0).(runtime.Object), args.Error(1)
}
@@ -142,13 +130,8 @@ func (m storageMock) Create(ctx context.Context, obj runtime.Object, createValid
}
args := m.Called(ctx, obj, createValidation, options)
acc, err := meta.Accessor(obj)
if err != nil {
return nil, args.Error(1)
}
name := acc.GetName()
if name == "object-fail" {
return nil, args.Error(1)
if err := args.Get(1); err != nil {
return nil, err.(error)
}
return args.Get(0).(runtime.Object), args.Error(1)
}
@@ -161,8 +144,8 @@ func (m storageMock) List(ctx context.Context, options *metainternalversion.List
}
args := m.Called(ctx, options)
if options.Kind == "fail" {
return nil, args.Error(1)
if err := args.Get(1); err != nil {
return nil, err.(error)
}
return args.Get(0).(runtime.Object), args.Error(1)
}
@@ -179,8 +162,8 @@ func (m storageMock) Update(ctx context.Context, name string, objInfo rest.Updat
}
args := m.Called(ctx, name, objInfo, createValidation, updateValidation, forceAllowCreate, options)
if name == "object-fail" {
return nil, false, args.Error(2)
if err := args.Get(2); err != nil {
return nil, false, err.(error)
}
return args.Get(0).(runtime.Object), args.Bool(1), args.Error(2)
}
@@ -193,8 +176,8 @@ func (m storageMock) Delete(ctx context.Context, name string, deleteValidation r
}
args := m.Called(ctx, name, deleteValidation, options)
if name == "object-fail" {
return nil, false, args.Error(2)
if err := args.Get(2); err != nil {
return nil, false, err.(error)
}
return args.Get(0).(runtime.Object), args.Bool(1), args.Error(2)
}
@@ -207,8 +190,8 @@ func (m storageMock) DeleteCollection(ctx context.Context, deleteValidation rest
}
args := m.Called(ctx, deleteValidation, options, listOptions)
if options.Kind == "fail" {
return nil, args.Error(1)
if err := args.Get(1); err != nil {
return nil, err.(error)
}
return args.Get(0).(runtime.Object), args.Error(1)
}