Change:
What happened: I created a namespace with finalizer, and then tried to delete it: The namespace can be deleted, and the finalizer is removed by garbage collection controller: What you expected to happen: Since kubectl species , it is not an orphaning operation, the finalizer should be removed in namespace store. How to reproduce it (as minimally and precisely as possible): Same as what I observed: a namespace. finalizer. . Anything else we need to know?: Environment: Kubernetes version (use ): HEAD
/sig api-machinery
cc
Discussion on slack:

Discussion:
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/fields:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/labels:go_default_library", <ins> "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library", </ins> "//staging/src/k8s.io/apiserver/pkg/endpoints/request:go_default_library", "//staging/src/k8s.io/apiserver/pkg/registry/generic:go_default_library", "//staging/src/k8s.io/apiserver/pkg/registry/generic/testing:go_default_library",

existingNamespace.Status.Phase = api.NamespaceTerminating } <del> // Remove orphan finalizer if options.OrphanDependents = false. if options.OrphanDependents != nil && *options.OrphanDependents == false { // remove Orphan finalizer. </del> <ins> // the current finalizers which are on namespace currentFinalizers := map[string]bool{} for _, f := range existingNamespace.Finalizers { currentFinalizers[f] = true } // the finalizers we should ensure on namespace shouldHaveFinalizers := map[string]bool{ metav1.FinalizerOrphanDependents: shouldHaveOrphanFinalizer(options, currentFinalizers[metav1.FinalizerOrphanDependents]), metav1.FinalizerDeleteDependents: shouldHaveDeleteDependentsFinalizer(options, currentFinalizers[metav1.FinalizerDeleteDependents]), } // determine whether there are changes changeNeeded := false for finalizer, shouldHave := range shouldHaveFinalizers { changeNeeded = currentFinalizers[finalizer] != shouldHave || changeNeeded if shouldHave { currentFinalizers[finalizer] = true } else { delete(currentFinalizers, finalizer) } } // make the changes if needed if changeNeeded { </ins> newFinalizers := []string{} <del> for i := range existingNamespace.ObjectMeta.Finalizers { finalizer := existingNamespace.ObjectMeta.Finalizers[i] if string(finalizer) != metav1.FinalizerOrphanDependents { newFinalizers = append(newFinalizers, finalizer) } </del> <ins> for f := range currentFinalizers { newFinalizers = append(newFinalizers, f) </ins> } <del> existingNamespace.ObjectMeta.Finalizers = newFinalizers </del> <ins> existingNamespace.Finalizers = newFinalizers </ins> } return existingNamespace, nil }),

return r.store.Delete(ctx, name, options) } <ins> func shouldHaveOrphanFinalizer(options *metav1.DeleteOptions, haveOrphanFinalizer bool) bool { if options.OrphanDependents != nil { return *options.OrphanDependents } if options.PropagationPolicy != nil { return *options.PropagationPolicy == metav1.DeletePropagationOrphan } return haveOrphanFinalizer } func shouldHaveDeleteDependentsFinalizer(options *metav1.DeleteOptions, haveDeleteDependentsFinalizer bool) bool { if options.OrphanDependents != nil { return *options.OrphanDependents == false } if options.PropagationPolicy != nil { return *options.PropagationPolicy == metav1.DeletePropagationForeground } return haveDeleteDependentsFinalizer } </ins> func (e *REST) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1beta1.Table, error) { return e.store.ConvertToTable(ctx, object, tableOptions) }

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" <ins> "k8s.io/apimachinery/pkg/runtime" </ins> genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/generic" genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing" "k8s.io/apiserver/pkg/registry/rest" etcdtesting "k8s.io/apiserver/pkg/storage/etcd/testing" api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/registry/registrytest" )

} } <ins> func TestDeleteWithGCFinalizers(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) defer storage.store.DestroyFunc() propagationBackground := metav1.DeletePropagationBackground propagationForeground := metav1.DeletePropagationForeground propagationOrphan := metav1.DeletePropagationOrphan trueVar := true var tests = []struct { name          string deleteOptions *metav1.DeleteOptions existingFinalizers  []string remainingFinalizers map[string]bool }{ { name:          "nil-with-orphan", deleteOptions: nil, existingFinalizers: []string{ metav1.FinalizerOrphanDependents, }, remainingFinalizers: map[string]bool{ metav1.FinalizerOrphanDependents: true, }, }, { name:          "nil-with-delete", deleteOptions: nil, existingFinalizers: []string{ metav1.FinalizerDeleteDependents, }, remainingFinalizers: map[string]bool{ metav1.FinalizerDeleteDependents: true, }, }, { name:                "nil-without-finalizers", deleteOptions:       nil, existingFinalizers:  []string{}, remainingFinalizers: map[string]bool{}, }, { name: "propagation-background-with-orphan", deleteOptions: &metav1.DeleteOptions{ PropagationPolicy: &propagationBackground, }, existingFinalizers: []string{ metav1.FinalizerOrphanDependents, }, remainingFinalizers: map[string]bool{}, }, { name: "propagation-background-with-delete", deleteOptions: &metav1.DeleteOptions{ PropagationPolicy: &propagationBackground, }, existingFinalizers: []string{ metav1.FinalizerDeleteDependents, }, remainingFinalizers: map[string]bool{}, }, { name: "propagation-background-without-finalizers", deleteOptions: &metav1.DeleteOptions{ PropagationPolicy: &propagationBackground, }, existingFinalizers:  []string{}, remainingFinalizers: map[string]bool{}, }, { name: "propagation-foreground-with-orphan", deleteOptions: &metav1.DeleteOptions{ PropagationPolicy: &propagationForeground, }, existingFinalizers: []string{ metav1.FinalizerOrphanDependents, }, remainingFinalizers: map[string]bool{ metav1.FinalizerDeleteDependents: true, }, }, { name: "propagation-foreground-with-delete", deleteOptions: &metav1.DeleteOptions{ PropagationPolicy: &propagationForeground, }, existingFinalizers: []string{ metav1.FinalizerDeleteDependents, }, remainingFinalizers: map[string]bool{ metav1.FinalizerDeleteDependents: true, }, }, { name: "propagation-foreground-without-finalizers", deleteOptions: &metav1.DeleteOptions{ PropagationPolicy: &propagationForeground, }, existingFinalizers: []string{}, remainingFinalizers: map[string]bool{ metav1.FinalizerDeleteDependents: true, }, }, { name: "propagation-orphan-with-orphan", deleteOptions: &metav1.DeleteOptions{ PropagationPolicy: &propagationOrphan, }, existingFinalizers: []string{ metav1.FinalizerOrphanDependents, }, remainingFinalizers: map[string]bool{ metav1.FinalizerOrphanDependents: true, }, }, { name: "propagation-orphan-with-delete", deleteOptions: &metav1.DeleteOptions{ PropagationPolicy: &propagationOrphan, }, existingFinalizers: []string{ metav1.FinalizerDeleteDependents, }, remainingFinalizers: map[string]bool{ metav1.FinalizerOrphanDependents: true, }, }, { name: "propagation-orphan-without-finalizers", deleteOptions: &metav1.DeleteOptions{ PropagationPolicy: &propagationOrphan, }, existingFinalizers: []string{}, remainingFinalizers: map[string]bool{ metav1.FinalizerOrphanDependents: true, }, }, { name: "orphan-dependents-with-orphan", deleteOptions: &metav1.DeleteOptions{ OrphanDependents: &trueVar, }, existingFinalizers: []string{ metav1.FinalizerOrphanDependents, }, remainingFinalizers: map[string]bool{ metav1.FinalizerOrphanDependents: true, }, }, { name: "orphan-dependents-with-delete", deleteOptions: &metav1.DeleteOptions{ OrphanDependents: &trueVar, }, existingFinalizers: []string{ metav1.FinalizerDeleteDependents, }, remainingFinalizers: map[string]bool{ metav1.FinalizerOrphanDependents: true, }, }, { name: "orphan-dependents-without-finalizers", deleteOptions: &metav1.DeleteOptions{ OrphanDependents: &trueVar, }, existingFinalizers: []string{}, remainingFinalizers: map[string]bool{ metav1.FinalizerOrphanDependents: true, }, }, } for _, test := range tests { key := "namespaces/" + test.name ctx := genericapirequest.NewContext() namespace := &api.Namespace{ ObjectMeta: metav1.ObjectMeta{ Name:       test.name, Finalizers: test.existingFinalizers, }, Spec: api.NamespaceSpec{ Finalizers: []api.FinalizerName{}, }, Status: api.NamespaceStatus{Phase: api.NamespaceActive}, } if err := storage.store.Storage.Create(ctx, key, namespace, nil, 0, false); err != nil { t.Fatalf("unexpected error: %v", err) } var obj runtime.Object var err error if obj, _, err = storage.Delete(ctx, test.name, test.deleteOptions); err != nil { t.Fatalf("unexpected error: %v", err) } ns, ok := obj.(*api.Namespace) if !ok { t.Errorf("unexpected object kind: %+v", obj) } if len(ns.Finalizers) != len(test.remainingFinalizers) { t.Errorf("%s: unexpected remaining finalizers: %v", test.name, ns.Finalizers) } for _, f := range ns.Finalizers { if test.remainingFinalizers[f] != true { t.Errorf("%s: unexpected finalizer %s", test.name, f) } } } } </ins> func TestShortNames(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t)