From ec343f276f429669831f23140ed61d6c08c36f3e Mon Sep 17 00:00:00 2001 From: Leonor Oliveira <9090754+leonorfmartins@users.noreply.github.com> Date: Wed, 24 Jul 2024 13:10:10 +0100 Subject: [PATCH] Dual writer: mode 4 (#90581) * Itroduce watcher and mode4 * Logging * Mode4 should be initialized from the dual writer for observability * Comment watch while it's not implemented * Lint * Use mode log when dual writer is initiated * Use error from logger --- pkg/apiserver/rest/dualwriter_mode1.go | 8 +- pkg/apiserver/rest/dualwriter_mode2.go | 9 +- pkg/apiserver/rest/dualwriter_mode3.go | 7 + pkg/apiserver/rest/dualwriter_mode4.go | 65 +++- pkg/apiserver/rest/dualwriter_mode4_test.go | 392 +++++++++++++++++--- pkg/services/apiserver/builder/helper.go | 6 +- 6 files changed, 414 insertions(+), 73 deletions(-) diff --git a/pkg/apiserver/rest/dualwriter_mode1.go b/pkg/apiserver/rest/dualwriter_mode1.go index 4801b75374c..933ad8ff0ed 100644 --- a/pkg/apiserver/rest/dualwriter_mode1.go +++ b/pkg/apiserver/rest/dualwriter_mode1.go @@ -9,6 +9,7 @@ import ( 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/watch" "k8s.io/apiserver/pkg/registry/rest" "k8s.io/klog/v2" ) @@ -25,7 +26,7 @@ const mode1Str = "1" // NewDualWriterMode1 returns a new DualWriter in mode 1. // Mode 1 represents writing to and reading from LegacyStorage. func newDualWriterMode1(legacy LegacyStorage, storage Storage, dwm *dualWriterMetrics) *DualWriterMode1 { - return &DualWriterMode1{Legacy: legacy, Storage: storage, Log: klog.NewKlogr().WithName("DualWriterMode1"), dualWriterMetrics: dwm} + return &DualWriterMode1{Legacy: legacy, Storage: storage, Log: klog.NewKlogr().WithName("DualWriterMode1").WithValues("mode", mode1Str), dualWriterMetrics: dwm} } // Mode returns the mode of the dual writer. @@ -274,6 +275,11 @@ func (d *DualWriterMode1) Destroy() { d.Legacy.Destroy() } +func (d *DualWriterMode1) Watch(ctx context.Context, options *metainternalversion.ListOptions) (watch.Interface, error) { + d.Log.Error(errors.New("Watch not implemented in mode 1"), "Watch not implemented in mode 1") + return nil, nil +} + func (d *DualWriterMode1) GetSingularName() string { return d.Legacy.GetSingularName() } diff --git a/pkg/apiserver/rest/dualwriter_mode2.go b/pkg/apiserver/rest/dualwriter_mode2.go index 91a5ed68223..95b4a93218b 100644 --- a/pkg/apiserver/rest/dualwriter_mode2.go +++ b/pkg/apiserver/rest/dualwriter_mode2.go @@ -2,6 +2,7 @@ package rest import ( "context" + "errors" "time" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -9,6 +10,7 @@ import ( 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/watch" "k8s.io/apiserver/pkg/registry/rest" "k8s.io/klog/v2" @@ -27,7 +29,7 @@ const mode2Str = "2" // NewDualWriterMode2 returns a new DualWriter in mode 2. // Mode 2 represents writing to LegacyStorage and Storage and reading from LegacyStorage. func newDualWriterMode2(legacy LegacyStorage, storage Storage, dwm *dualWriterMetrics) *DualWriterMode2 { - return &DualWriterMode2{Legacy: legacy, Storage: storage, Log: klog.NewKlogr().WithName("DualWriterMode2"), dualWriterMetrics: dwm} + return &DualWriterMode2{Legacy: legacy, Storage: storage, Log: klog.NewKlogr().WithName("DualWriterMode2").WithValues("mode", mode2Str), dualWriterMetrics: dwm} } // Mode returns the mode of the dual writer. @@ -332,6 +334,11 @@ func (d *DualWriterMode2) Update(ctx context.Context, name string, objInfo rest. return res, created, err } +func (d *DualWriterMode2) Watch(ctx context.Context, options *metainternalversion.ListOptions) (watch.Interface, error) { + d.Log.Error(errors.New("Watch not implemented in mode 2"), "Watch not implemented in mode 2") + return nil, nil +} + func (d *DualWriterMode2) Destroy() { d.Storage.Destroy() d.Legacy.Destroy() diff --git a/pkg/apiserver/rest/dualwriter_mode3.go b/pkg/apiserver/rest/dualwriter_mode3.go index 5d1c80df90f..00c6aa4ef05 100644 --- a/pkg/apiserver/rest/dualwriter_mode3.go +++ b/pkg/apiserver/rest/dualwriter_mode3.go @@ -169,6 +169,13 @@ func (d *DualWriterMode3) DeleteCollection(ctx context.Context, deleteValidation return res, err } +//TODO: uncomment when storage watch is implemented +// func (d *DualWriterMode3) Watch(ctx context.Context, options *metainternalversion.ListOptions) (watch.Interface, error) { +// var method = "watch" +// d.Log.WithValues("kind", options.Kind, "method", method, "mode", mode3Str).Info("starting to watch") +// return d.Storage.Watch(ctx, options) +// } + func (d *DualWriterMode3) Destroy() { d.Storage.Destroy() d.Legacy.Destroy() diff --git a/pkg/apiserver/rest/dualwriter_mode4.go b/pkg/apiserver/rest/dualwriter_mode4.go index ebb1799a722..ae3a96da3c7 100644 --- a/pkg/apiserver/rest/dualwriter_mode4.go +++ b/pkg/apiserver/rest/dualwriter_mode4.go @@ -17,10 +17,12 @@ type DualWriterMode4 struct { Log klog.Logger } +const mode4Str = "4" + // newDualWriterMode4 returns a new DualWriter in mode 4. // Mode 4 represents writing and reading from Storage. func newDualWriterMode4(legacy LegacyStorage, storage Storage, dwm *dualWriterMetrics) *DualWriterMode4 { - return &DualWriterMode4{Legacy: legacy, Storage: storage, Log: klog.NewKlogr().WithName("DualWriterMode4"), dualWriterMetrics: dwm} + return &DualWriterMode4{Legacy: legacy, Storage: storage, Log: klog.NewKlogr().WithName("DualWriterMode4").WithValues("mode", mode4Str), dualWriterMetrics: dwm} } // Mode returns the mode of the dual writer. @@ -32,32 +34,81 @@ func (d *DualWriterMode4) Mode() DualWriterMode { // Create overrides the behavior of the generic DualWriter and writes only to Storage. func (d *DualWriterMode4) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) { - return d.Storage.Create(ctx, obj, createValidation, options) + var method = "create" + log := d.Log.WithValues("kind", options.Kind, "method", method) + ctx = klog.NewContext(ctx, log) + res, err := d.Storage.Create(ctx, obj, createValidation, options) + if err != nil { + log.Error(err, "unable to create object in storage") + } + return res, err } // Get overrides the behavior of the generic DualWriter and retrieves an object from Storage. func (d *DualWriterMode4) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { - return d.Storage.Get(ctx, name, &metav1.GetOptions{}) + var method = "get" + log := d.Log.WithValues("kind", options.Kind, "method", method) + ctx = klog.NewContext(ctx, log) + res, err := d.Storage.Get(ctx, name, options) + if err != nil { + log.Error(err, "unable to create object in storage") + } + return res, err } func (d *DualWriterMode4) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) { - return d.Storage.Delete(ctx, name, deleteValidation, options) + var method = "delete" + log := d.Log.WithValues("name", name, "kind", options.Kind, "method", method) + ctx = klog.NewContext(ctx, log) + res, async, err := d.Storage.Delete(ctx, name, deleteValidation, options) + if err != nil { + log.Error(err, "unable to delete object in storage") + } + return res, async, err } // DeleteCollection overrides the behavior of the generic DualWriter and deletes only from Storage. func (d *DualWriterMode4) DeleteCollection(ctx context.Context, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions, listOptions *metainternalversion.ListOptions) (runtime.Object, error) { - return d.Storage.DeleteCollection(ctx, deleteValidation, options, listOptions) + var method = "delete-collection" + log := d.Log.WithValues("kind", options.Kind, "resourceVersion", listOptions.ResourceVersion, "method", method, "mode", mode4Str) + ctx = klog.NewContext(ctx, log) + res, err := d.Storage.DeleteCollection(ctx, deleteValidation, options, listOptions) + if err != nil { + log.Error(err, "unable to delete collection in storage") + } + return res, err } // Update overrides the generic behavior of the Storage and writes only to US. func (d *DualWriterMode4) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) { - return d.Storage.Update(ctx, name, objInfo, createValidation, updateValidation, forceAllowCreate, options) + var method = "update" + log := d.Log.WithValues("name", name, "kind", options.Kind, "method", method) + ctx = klog.NewContext(ctx, log) + res, async, err := d.Storage.Update(ctx, name, objInfo, createValidation, updateValidation, forceAllowCreate, options) + if err != nil { + log.Error(err, "unable to update object in storage") + } + return res, async, err } func (d *DualWriterMode4) List(ctx context.Context, options *metainternalversion.ListOptions) (runtime.Object, error) { - return d.Storage.List(ctx, options) + var method = "list" + log := d.Log.WithValues("kind", options.Kind, "resourceVersion", options.ResourceVersion, "kind", options.Kind, "method", method) + ctx = klog.NewContext(ctx, log) + res, err := d.Storage.List(ctx, options) + if err != nil { + log.Error(err, "unable to list objects in storage") + } + return res, err } +//TODO: uncomment when storage watch is implemented +// func (d *DualWriterMode4) Watch(ctx context.Context, options *metainternalversion.ListOptions) (watch.Interface, error) { +// var method = "watch" +// d.Log.WithValues("kind", options.Kind, "method", method, "mode", mode4Str).Info("starting to watch") +// return d.Storage.Watch(ctx, options) +// } + func (d *DualWriterMode4) Destroy() { d.Storage.Destroy() } diff --git a/pkg/apiserver/rest/dualwriter_mode4_test.go b/pkg/apiserver/rest/dualwriter_mode4_test.go index cb741b86a04..bddaf4a7995 100644 --- a/pkg/apiserver/rest/dualwriter_mode4_test.go +++ b/pkg/apiserver/rest/dualwriter_mode4_test.go @@ -1,72 +1,346 @@ package rest -// import ( -// "context" -// "testing" +import ( + "context" + "errors" + "testing" -// "github.com/stretchr/testify/assert" -// metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" -// metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -// "k8s.io/apimachinery/pkg/runtime" -// "k8s.io/apiserver/pkg/apis/example" -// ) + "github.com/prometheus/client_golang/prometheus" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "k8s.io/apimachinery/pkg/api/meta" + metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) -// func TestMode4(t *testing.T) { -// var ls = (LegacyStorage)(nil) -// var s = (Storage)(nil) -// lsSpy := NewLegacyStorageSpyClient(ls) -// sSpy := NewStorageSpyClient(s) +func TestMode4_Create(t *testing.T) { + type testCase struct { + input runtime.Object + setupStorageFn func(m *mock.Mock, input runtime.Object) + name string + wantErr bool + } + tests := + []testCase{ + { + name: "creating an object only in the unified store", + input: exampleObj, + setupStorageFn: func(m *mock.Mock, input runtime.Object) { + m.On("Create", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(exampleObj, nil) + }, + }, + { + name: "error when creating object in the unified store fails", + input: failingObj, + setupStorageFn: func(m *mock.Mock, input runtime.Object) { + m.On("Create", mock.Anything, input, mock.Anything, mock.Anything).Return(nil, errors.New("error")) + }, + wantErr: true, + }, + } -// dw := NewDualWriterMode4(lsSpy, sSpy) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + l := (LegacyStorage)(nil) + s := (Storage)(nil) + m := &mock.Mock{} -// // Create: it should use the Legacy Create implementation -// _, err := dw.Create(context.Background(), &dummyObject{}, func(context.Context, runtime.Object) error { return nil }, &metav1.CreateOptions{}) -// assert.NoError(t, err) -// assert.Equal(t, 0, lsSpy.Counts("LegacyStorage.Create")) -// assert.Equal(t, 1, sSpy.Counts("Storage.Create")) + ls := legacyStoreMock{m, l} + us := storageMock{m, s} -// // Get: it should use the Storage Get implementation -// _, err = dw.Get(context.Background(), kind, &metav1.GetOptions{}) -// assert.NoError(t, err) -// assert.Equal(t, 0, lsSpy.Counts("LegacyStorage.Get")) -// assert.Equal(t, 1, sSpy.Counts("Storage.Get")) + if tt.setupStorageFn != nil { + tt.setupStorageFn(m, tt.input) + } -// // List: it should use the Storage Get implementation -// _, err = dw.List(context.Background(), &metainternalversion.ListOptions{}) -// assert.NoError(t, err) -// assert.Equal(t, 0, lsSpy.Counts("LegacyStorage.List")) -// assert.Equal(t, 1, sSpy.Counts("Storage.List")) + dw := NewDualWriter(Mode4, ls, us, p) -// // Delete: it should use call Storage Delete method -// var deleteValidation = func(ctx context.Context, obj runtime.Object) error { return nil } -// _, _, err = dw.Delete(context.Background(), kind, deleteValidation, &metav1.DeleteOptions{}) -// assert.NoError(t, err) -// assert.Equal(t, 0, lsSpy.Counts("LegacyStorage.Delete")) -// assert.Equal(t, 1, sSpy.Counts("Storage.Delete")) + obj, err := dw.Create(context.Background(), tt.input, func(context.Context, runtime.Object) error { return nil }, &metav1.CreateOptions{}) -// // DeleteCollection: it should use the Storage DeleteCollection implementation -// _, err = dw.DeleteCollection( -// context.Background(), -// func(context.Context, runtime.Object) error { return nil }, -// &metav1.DeleteOptions{}, -// &metainternalversion.ListOptions{}, -// ) -// assert.NoError(t, err) -// assert.Equal(t, 0, lsSpy.Counts("LegacyStorage.DeleteCollection")) -// assert.Equal(t, 1, sSpy.Counts("Storage.DeleteCollection")) + if tt.wantErr { + assert.Error(t, err) + return + } -// // Update: it should update only in Storage -// dummy := &example.Pod{} -// uoi := UpdatedObjInfoObj{} -// _, err = uoi.UpdatedObject(context.Background(), dummy) -// assert.NoError(t, err) + acc, err := meta.Accessor(obj) + assert.NoError(t, err) + assert.Equal(t, acc.GetResourceVersion(), "1") + assert.NotEqual(t, obj, anotherObj) + }) + } +} -// var validateObjFn = func(ctx context.Context, obj runtime.Object) error { return nil } -// var validateObjUpdateFn = func(ctx context.Context, obj, old runtime.Object) error { return nil } +func TestMode4_Get(t *testing.T) { + type testCase struct { + setupStorageFn func(m *mock.Mock, name string) + name string + input string + wantErr bool + } + tests := + []testCase{ + { + name: "get an object only in unified store", + input: "foo", + setupStorageFn: func(m *mock.Mock, name string) { + m.On("Get", mock.Anything, name, mock.Anything).Return(exampleObj, nil) + }, + }, + { + name: "error when getting an object in the inified store fails", + input: "object-fail", + setupStorageFn: func(m *mock.Mock, name string) { + m.On("Get", mock.Anything, name, mock.Anything).Return(nil, errors.New("error")) + }, + wantErr: true, + }, + } -// _, _, err = dw.Update(context.Background(), kind, uoi, validateObjFn, validateObjUpdateFn, false, &metav1.UpdateOptions{}) -// assert.NoError(t, err) -// assert.Equal(t, 0, lsSpy.Counts("LegacyStorage.Update")) -// assert.Equal(t, 1, sSpy.Counts("Storage.Update")) -// assert.NoError(t, err) -// } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + l := (LegacyStorage)(nil) + s := (Storage)(nil) + m := &mock.Mock{} + + ls := legacyStoreMock{m, l} + us := storageMock{m, s} + + if tt.setupStorageFn != nil { + tt.setupStorageFn(m, tt.input) + } + + p := prometheus.NewRegistry() + dw := NewDualWriter(Mode4, 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 TestMode4_List(t *testing.T) { + type testCase struct { + setupStorageFn func(m *mock.Mock, options *metainternalversion.ListOptions) + name string + options *metainternalversion.ListOptions + wantErr bool + } + tests := + []testCase{ + { + name: "error when listing an object in the unified store is not implemented", + options: &metainternalversion.ListOptions{TypeMeta: metav1.TypeMeta{Kind: "fail"}}, + setupStorageFn: func(m *mock.Mock, options *metainternalversion.ListOptions) { + m.On("List", mock.Anything, options).Return(nil, errors.New("error")) + }, + wantErr: true, + }, + { + name: "list objects in the unified store", + options: &metainternalversion.ListOptions{TypeMeta: metav1.TypeMeta{Kind: "foo"}}, + setupStorageFn: func(m *mock.Mock, options *metainternalversion.ListOptions) { + m.On("List", mock.Anything, options).Return(exampleList, nil) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + l := (LegacyStorage)(nil) + s := (Storage)(nil) + m := &mock.Mock{} + + ls := legacyStoreMock{m, l} + us := storageMock{m, s} + + if tt.setupStorageFn != nil { + tt.setupStorageFn(m, tt.options) + } + + dw := NewDualWriter(Mode4, ls, us, p) + + res, err := dw.List(context.Background(), tt.options) + + if tt.wantErr { + assert.Error(t, err) + return + } + + assert.Equal(t, exampleList, res) + assert.NotEqual(t, anotherList, res) + }) + } +} + +func TestMode4_Delete(t *testing.T) { + type testCase struct { + setupStorageFn func(m *mock.Mock, name string) + name string + input string + wantErr bool + } + tests := + []testCase{ + { + name: "deleting an object in the unified store", + input: "foo", + setupStorageFn: func(m *mock.Mock, name string) { + m.On("Delete", mock.Anything, name, mock.Anything, mock.Anything).Return(exampleObj, false, nil) + }, + }, + { + name: "error when deleting an object in the unified store", + input: "object-fail", + setupStorageFn: func(m *mock.Mock, name string) { + m.On("Delete", mock.Anything, name, mock.Anything, mock.Anything).Return(nil, false, errors.New("error")) + }, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + l := (LegacyStorage)(nil) + s := (Storage)(nil) + m := &mock.Mock{} + + ls := legacyStoreMock{m, l} + us := storageMock{m, s} + + if tt.setupStorageFn != nil { + tt.setupStorageFn(m, tt.input) + } + + dw := NewDualWriter(Mode4, 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 TestMode4_DeleteCollection(t *testing.T) { + type testCase struct { + input *metav1.DeleteOptions + setupStorageFn func(m *mock.Mock, input *metav1.DeleteOptions) + name string + wantErr bool + } + tests := + []testCase{ + { + name: "deleting a collection in the unified store", + input: &metav1.DeleteOptions{TypeMeta: metav1.TypeMeta{Kind: "foo"}}, + setupStorageFn: func(m *mock.Mock, input *metav1.DeleteOptions) { + m.On("DeleteCollection", mock.Anything, mock.Anything, input, mock.Anything).Return(exampleObj, nil) + }, + }, + { + name: "error deleting a collection in the unified store", + input: &metav1.DeleteOptions{TypeMeta: metav1.TypeMeta{Kind: "fail"}}, + setupStorageFn: func(m *mock.Mock, input *metav1.DeleteOptions) { + m.On("DeleteCollection", mock.Anything, mock.Anything, input, mock.Anything).Return(nil, errors.New("error")) + }, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + l := (LegacyStorage)(nil) + s := (Storage)(nil) + m := &mock.Mock{} + + ls := legacyStoreMock{m, l} + us := storageMock{m, s} + + if tt.setupStorageFn != nil { + tt.setupStorageFn(m, tt.input) + } + + dw := NewDualWriter(Mode4, 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 TestMode4_Update(t *testing.T) { + type testCase struct { + setupStorageFn func(m *mock.Mock, input string) + name string + input string + wantErr bool + } + tests := + []testCase{ + { + name: "update an object in unified store", + input: "foo", + setupStorageFn: func(m *mock.Mock, input string) { + m.On("Update", mock.Anything, input, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(exampleObj, false, nil) + }, + }, + { + name: "error updating an object in unified store", + input: "object-fail", + setupStorageFn: func(m *mock.Mock, input string) { + m.On("Update", mock.Anything, input, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, false, errors.New("error")) + }, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + l := (LegacyStorage)(nil) + s := (Storage)(nil) + m := &mock.Mock{} + + ls := legacyStoreMock{m, l} + us := storageMock{m, s} + + if tt.setupStorageFn != nil { + tt.setupStorageFn(m, tt.input) + } + + dw := NewDualWriter(Mode4, 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) + }) + } +} diff --git a/pkg/services/apiserver/builder/helper.go b/pkg/services/apiserver/builder/helper.go index 0878abced18..d0cc628fbcf 100644 --- a/pkg/services/apiserver/builder/helper.go +++ b/pkg/services/apiserver/builder/helper.go @@ -174,12 +174,8 @@ func InstallAPIs( if err != nil { return nil, err } - switch currentMode { - case grafanarest.Mode0: + if currentMode == grafanarest.Mode0 { return legacy, nil - case grafanarest.Mode4: - return storage, nil - default: } return grafanarest.NewDualWriter(currentMode, legacy, storage, reg), nil }