diff --git a/pkg/apiserver/rest/dualwriter.go b/pkg/apiserver/rest/dualwriter.go index 4d1dff0c35a..0e58dff152d 100644 --- a/pkg/apiserver/rest/dualwriter.go +++ b/pkg/apiserver/rest/dualwriter.go @@ -69,6 +69,7 @@ type DualWriter struct { var errDualWriterCreaterMissing = errors.New("legacy storage rest.Creater is missing") var errDualWriterListerMissing = errors.New("legacy storage rest.Lister is missing") var errDualWriterDeleterMissing = errors.New("legacy storage rest.GracefulDeleter is missing") +var errDualWriterCollectionDeleterMissing = errors.New("legacy storage rest.CollectionDeleter is missing") type DualWriterMode int diff --git a/pkg/apiserver/rest/dualwriter_mode1.go b/pkg/apiserver/rest/dualwriter_mode1.go index a36a01c8139..bc8537b9493 100644 --- a/pkg/apiserver/rest/dualwriter_mode1.go +++ b/pkg/apiserver/rest/dualwriter_mode1.go @@ -54,3 +54,13 @@ func (d *DualWriterMode1) Delete(ctx context.Context, name string, deleteValidat return legacy.Delete(ctx, name, deleteValidation, options) } + +// DeleteCollection overrides the behavior of the generic DualWriter and deletes only from LegacyStorage. +func (d *DualWriterMode1) DeleteCollection(ctx context.Context, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions, listOptions *metainternalversion.ListOptions) (runtime.Object, error) { + legacy, ok := d.Legacy.(rest.CollectionDeleter) + if !ok { + return nil, errDualWriterCollectionDeleterMissing + } + + return legacy.DeleteCollection(ctx, deleteValidation, options, listOptions) +} diff --git a/pkg/apiserver/rest/dualwriter_mode1_test.go b/pkg/apiserver/rest/dualwriter_mode1_test.go index cca3db15f36..04c0b79bd5b 100644 --- a/pkg/apiserver/rest/dualwriter_mode1_test.go +++ b/pkg/apiserver/rest/dualwriter_mode1_test.go @@ -44,4 +44,15 @@ func TestMode1(t *testing.T) { assert.NoError(t, err) assert.Equal(t, 1, lsSpy.Counts("LegacyStorage.Delete")) assert.Equal(t, 0, sSpy.Counts("Storage.Delete")) + + // DeleteCollection: it should use the Legacy 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, 1, lsSpy.Counts("LegacyStorage.DeleteCollection")) + assert.Equal(t, 0, sSpy.Counts("Storage.DeleteCollection")) } diff --git a/pkg/apiserver/rest/dualwriter_mode2.go b/pkg/apiserver/rest/dualwriter_mode2.go index 85e6ac551ea..efef61de2a1 100644 --- a/pkg/apiserver/rest/dualwriter_mode2.go +++ b/pkg/apiserver/rest/dualwriter_mode2.go @@ -124,25 +124,25 @@ func (d *DualWriterMode2) List(ctx context.Context, options *metainternalversion return ll, nil } -func enrichObject(orig, copy runtime.Object) (runtime.Object, error) { - accessorC, err := meta.Accessor(copy) +// DeleteCollection overrides the behavior of the generic DualWriter and deletes from both LegacyStorage and Storage. +func (d *DualWriterMode2) DeleteCollection(ctx context.Context, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions, listOptions *metainternalversion.ListOptions) (runtime.Object, error) { + legacy, ok := d.Legacy.(rest.CollectionDeleter) + if !ok { + return nil, errDualWriterCollectionDeleterMissing + } + + // #TODO: figure out how to handle partial deletions + deleted, err := legacy.DeleteCollection(ctx, deleteValidation, options, listOptions) if err != nil { - return nil, err + klog.FromContext(ctx).Error(err, "failed to delete collection successfully from legacy storage", "deletedObjects", deleted) } - accessorO, err := meta.Accessor(orig) + + res, err := d.Storage.DeleteCollection(ctx, deleteValidation, options, listOptions) if err != nil { - return nil, err + klog.FromContext(ctx).Error(err, "failed to delete collection successfully from Storage", "deletedObjects", deleted) } - accessorC.SetLabels(accessorO.GetLabels()) - - ac := accessorC.GetAnnotations() - for k, v := range accessorO.GetAnnotations() { - ac[k] = v - } - accessorC.SetAnnotations(ac) - - return copy, nil + return res, err } func (d *DualWriterMode2) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) { @@ -168,3 +168,24 @@ func (d *DualWriterMode2) Delete(ctx context.Context, name string, deleteValidat return deletedLS, async, err } + +func enrichObject(orig, copy runtime.Object) (runtime.Object, error) { + accessorC, err := meta.Accessor(copy) + if err != nil { + return nil, err + } + accessorO, err := meta.Accessor(orig) + if err != nil { + return nil, err + } + + accessorC.SetLabels(accessorO.GetLabels()) + + ac := accessorC.GetAnnotations() + for k, v := range accessorO.GetAnnotations() { + ac[k] = v + } + accessorC.SetAnnotations(ac) + + return copy, nil +} diff --git a/pkg/apiserver/rest/dualwriter_mode2_test.go b/pkg/apiserver/rest/dualwriter_mode2_test.go index eb3e640528d..76dd25dc7e8 100644 --- a/pkg/apiserver/rest/dualwriter_mode2_test.go +++ b/pkg/apiserver/rest/dualwriter_mode2_test.go @@ -68,4 +68,15 @@ func TestMode2(t *testing.T) { assert.NoError(t, err) assert.Equal(t, 1, lsSpy.Counts("LegacyStorage.Delete")) assert.Equal(t, 1, sSpy.Counts("Storage.Delete")) + + // DeleteCollection: it should delete from both LegacyStorage and Storage + _, err = dw.DeleteCollection( + context.Background(), + func(context.Context, runtime.Object) error { return nil }, + &metav1.DeleteOptions{}, + &metainternalversion.ListOptions{}, + ) + assert.NoError(t, err) + assert.Equal(t, 1, lsSpy.Counts("LegacyStorage.DeleteCollection")) + assert.Equal(t, 1, sSpy.Counts("Storage.DeleteCollection")) } diff --git a/pkg/apiserver/rest/dualwriter_mode3.go b/pkg/apiserver/rest/dualwriter_mode3.go index e4caf32347b..89c868c14e3 100644 --- a/pkg/apiserver/rest/dualwriter_mode3.go +++ b/pkg/apiserver/rest/dualwriter_mode3.go @@ -4,6 +4,7 @@ import ( "context" apierrors "k8s.io/apimachinery/pkg/api/errors" + metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/registry/rest" @@ -66,3 +67,23 @@ func (d *DualWriterMode3) Delete(ctx context.Context, name string, deleteValidat return deleted, async, err } + +// DeleteCollection overrides the behavior of the generic DualWriter and deletes from both LegacyStorage and Storage. +func (d *DualWriterMode3) DeleteCollection(ctx context.Context, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions, listOptions *metainternalversion.ListOptions) (runtime.Object, error) { + legacy, ok := d.Legacy.(rest.CollectionDeleter) + if !ok { + return nil, errDualWriterCollectionDeleterMissing + } + + // #TODO: figure out how to handle partial deletions + deleted, err := d.Storage.DeleteCollection(ctx, deleteValidation, options, listOptions) + if err != nil { + klog.FromContext(ctx).Error(err, "failed to delete collection successfully from Storage", "deletedObjects", deleted) + } + + if deleted, err := legacy.DeleteCollection(ctx, deleteValidation, options, listOptions); err != nil { + klog.FromContext(ctx).Error(err, "failed to delete collection successfully from LegacyStorage", "deletedObjects", deleted) + } + + return deleted, err +} diff --git a/pkg/apiserver/rest/dualwriter_mode3_test.go b/pkg/apiserver/rest/dualwriter_mode3_test.go index 5e1ee9f09d0..135c8b72bcd 100644 --- a/pkg/apiserver/rest/dualwriter_mode3_test.go +++ b/pkg/apiserver/rest/dualwriter_mode3_test.go @@ -42,4 +42,15 @@ func TestMode3(t *testing.T) { assert.NoError(t, err) assert.Equal(t, 1, lsSpy.Counts("LegacyStorage.Delete")) assert.Equal(t, 1, sSpy.Counts("Storage.Delete")) + + // DeleteCollection: it should delete from both LegacyStorage and Storage + _, err = dw.DeleteCollection( + context.Background(), + func(context.Context, runtime.Object) error { return nil }, + &metav1.DeleteOptions{}, + &metainternalversion.ListOptions{}, + ) + assert.NoError(t, err) + assert.Equal(t, 1, lsSpy.Counts("LegacyStorage.DeleteCollection")) + assert.Equal(t, 1, sSpy.Counts("Storage.DeleteCollection")) } diff --git a/pkg/apiserver/rest/dualwriter_mode4.go b/pkg/apiserver/rest/dualwriter_mode4.go index fd8472cf400..993c57b1183 100644 --- a/pkg/apiserver/rest/dualwriter_mode4.go +++ b/pkg/apiserver/rest/dualwriter_mode4.go @@ -3,6 +3,7 @@ package rest import ( "context" + 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/registry/rest" @@ -33,3 +34,8 @@ func (d *DualWriterMode4) Get(ctx context.Context, name string, options *metav1. 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) } + +// 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) +} diff --git a/pkg/apiserver/rest/dualwriter_mode4_test.go b/pkg/apiserver/rest/dualwriter_mode4_test.go index a051f80f688..f95ec7c388e 100644 --- a/pkg/apiserver/rest/dualwriter_mode4_test.go +++ b/pkg/apiserver/rest/dualwriter_mode4_test.go @@ -42,4 +42,15 @@ func TestMode4(t *testing.T) { assert.NoError(t, err) assert.Equal(t, 0, lsSpy.Counts("LegacyStorage.Delete")) assert.Equal(t, 1, sSpy.Counts("Storage.Delete")) + + // 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")) } diff --git a/pkg/apiserver/rest/spy_client.go b/pkg/apiserver/rest/spy_client.go index 3ba4f92b4f0..b3c853139ea 100644 --- a/pkg/apiserver/rest/spy_client.go +++ b/pkg/apiserver/rest/spy_client.go @@ -101,6 +101,12 @@ func (c *spyStorageClient) Delete(ctx context.Context, name string, deleteValida return nil, false, nil } +func (c *spyStorageClient) DeleteCollection(ctx context.Context, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions, listOptions *metainternalversion.ListOptions) (runtime.Object, error) { + c.spy.record("Storage.DeleteCollection") + klog.Info("method: Storage.DeleteCollection") + return nil, nil +} + // LegacyStorage Spy type LegacyStorageSpyClient interface { @@ -184,6 +190,12 @@ func (c *spyLegacyStorageClient) Delete(ctx context.Context, name string, delete return nil, false, nil } +func (c *spyLegacyStorageClient) DeleteCollection(ctx context.Context, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions, listOptions *metainternalversion.ListOptions) (runtime.Object, error) { + c.spy.record("LegacyStorage.DeleteCollection") + klog.Info("method: LegacyStorage.DeleteCollection") + return nil, nil +} + type dummyList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"`