diff --git a/federation/pkg/federation-controller/configmap/configmap_controller_test.go b/federation/pkg/federation-controller/configmap/configmap_controller_test.go index 465b790d221..597f8416983 100644 --- a/federation/pkg/federation-controller/configmap/configmap_controller_test.go +++ b/federation/pkg/federation-controller/configmap/configmap_controller_test.go @@ -104,7 +104,7 @@ func TestConfigMapController(t *testing.T) { // There should be 2 updates to add both the finalizers. updatedConfigMap := GetConfigMapFromChan(configmapUpdateChan) assert.True(t, configmapController.hasFinalizerFunc(updatedConfigMap, deletionhelper.FinalizerDeleteFromUnderlyingClusters)) - assert.True(t, configmapController.hasFinalizerFunc(updatedConfigMap, metav1.FinalizerOrphan)) + assert.True(t, configmapController.hasFinalizerFunc(updatedConfigMap, metav1.FinalizerOrphanDependents)) // Verify that the configmap is created in underlying cluster1. createdConfigMap := GetConfigMapFromChan(cluster1CreateChan) diff --git a/federation/pkg/federation-controller/daemonset/daemonset_controller_test.go b/federation/pkg/federation-controller/daemonset/daemonset_controller_test.go index ef01b8d30a7..15e1d4c8820 100644 --- a/federation/pkg/federation-controller/daemonset/daemonset_controller_test.go +++ b/federation/pkg/federation-controller/daemonset/daemonset_controller_test.go @@ -105,7 +105,7 @@ func TestDaemonSetController(t *testing.T) { // There should be an update to add both the finalizers. updatedDaemonSet := GetDaemonSetFromChan(daemonsetUpdateChan) assert.True(t, daemonsetController.hasFinalizerFunc(updatedDaemonSet, deletionhelper.FinalizerDeleteFromUnderlyingClusters)) - assert.True(t, daemonsetController.hasFinalizerFunc(updatedDaemonSet, metav1.FinalizerOrphan)) + assert.True(t, daemonsetController.hasFinalizerFunc(updatedDaemonSet, metav1.FinalizerOrphanDependents)) daemonset1 = *updatedDaemonSet createdDaemonSet := GetDaemonSetFromChan(cluster1CreateChan) diff --git a/federation/pkg/federation-controller/ingress/ingress_controller_test.go b/federation/pkg/federation-controller/ingress/ingress_controller_test.go index df0145c5138..d4b3d66be7a 100644 --- a/federation/pkg/federation-controller/ingress/ingress_controller_test.go +++ b/federation/pkg/federation-controller/ingress/ingress_controller_test.go @@ -146,7 +146,7 @@ func TestIngressController(t *testing.T) { // There should be an update to add both the finalizers. updatedIngress := GetIngressFromChan(t, fedIngressUpdateChan) assert.True(t, ingressController.hasFinalizerFunc(updatedIngress, deletionhelper.FinalizerDeleteFromUnderlyingClusters)) - assert.True(t, ingressController.hasFinalizerFunc(updatedIngress, metav1.FinalizerOrphan), fmt.Sprintf("ingress does not have the orphan finalizer: %v", updatedIngress)) + assert.True(t, ingressController.hasFinalizerFunc(updatedIngress, metav1.FinalizerOrphanDependents), fmt.Sprintf("ingress does not have the orphan finalizer: %v", updatedIngress)) fedIngress = *updatedIngress t.Log("Checking that Ingress was correctly created in cluster 1") @@ -319,7 +319,7 @@ func WaitForFinalizersInFederationStore(ingressController *IngressController, st return false, err } ingress := obj.(*extensionsv1beta1.Ingress) - if ingressController.hasFinalizerFunc(ingress, metav1.FinalizerOrphan) && + if ingressController.hasFinalizerFunc(ingress, metav1.FinalizerOrphanDependents) && ingressController.hasFinalizerFunc(ingress, deletionhelper.FinalizerDeleteFromUnderlyingClusters) { return true, nil } diff --git a/federation/pkg/federation-controller/namespace/namespace_controller_test.go b/federation/pkg/federation-controller/namespace/namespace_controller_test.go index fe022c1a9c6..654a9efd388 100644 --- a/federation/pkg/federation-controller/namespace/namespace_controller_test.go +++ b/federation/pkg/federation-controller/namespace/namespace_controller_test.go @@ -133,7 +133,7 @@ func TestNamespaceController(t *testing.T) { // Delete the namespace with orphan finalizer (let namespaces // in underlying clusters be as is). // TODO: Add a test without orphan finalizer. - ns1.ObjectMeta.Finalizers = append(ns1.ObjectMeta.Finalizers, metav1.FinalizerOrphan) + ns1.ObjectMeta.Finalizers = append(ns1.ObjectMeta.Finalizers, metav1.FinalizerOrphanDependents) ns1.DeletionTimestamp = &metav1.Time{Time: time.Now()} namespaceWatch.Modify(&ns1) assert.Equal(t, ns1.Name, GetStringFromChan(nsDeleteChan)) diff --git a/federation/pkg/federation-controller/secret/secret_controller_test.go b/federation/pkg/federation-controller/secret/secret_controller_test.go index 5532a8025c8..bb71d343424 100644 --- a/federation/pkg/federation-controller/secret/secret_controller_test.go +++ b/federation/pkg/federation-controller/secret/secret_controller_test.go @@ -105,7 +105,7 @@ func TestSecretController(t *testing.T) { // There should be an update to add both the finalizers. updatedSecret := GetSecretFromChan(secretUpdateChan) assert.True(t, secretController.hasFinalizerFunc(updatedSecret, deletionhelper.FinalizerDeleteFromUnderlyingClusters)) - assert.True(t, secretController.hasFinalizerFunc(updatedSecret, metav1.FinalizerOrphan)) + assert.True(t, secretController.hasFinalizerFunc(updatedSecret, metav1.FinalizerOrphanDependents)) secret1 = *updatedSecret // Verify that the secret is created in underlying cluster1. diff --git a/federation/pkg/federation-controller/util/deletionhelper/deletion_helper.go b/federation/pkg/federation-controller/util/deletionhelper/deletion_helper.go index 528f017edfa..06adf8332d1 100644 --- a/federation/pkg/federation-controller/util/deletionhelper/deletion_helper.go +++ b/federation/pkg/federation-controller/util/deletionhelper/deletion_helper.go @@ -93,8 +93,8 @@ func (dh *DeletionHelper) EnsureFinalizers(obj runtime.Object) ( if !dh.hasFinalizerFunc(obj, FinalizerDeleteFromUnderlyingClusters) { finalizers = append(finalizers, FinalizerDeleteFromUnderlyingClusters) } - if !dh.hasFinalizerFunc(obj, metav1.FinalizerOrphan) { - finalizers = append(finalizers, metav1.FinalizerOrphan) + if !dh.hasFinalizerFunc(obj, metav1.FinalizerOrphanDependents) { + finalizers = append(finalizers, metav1.FinalizerOrphanDependents) } if len(finalizers) != 0 { glog.V(2).Infof("Adding finalizers %v to %s", finalizers, dh.objNameFunc(obj)) @@ -117,7 +117,7 @@ func (dh *DeletionHelper) HandleObjectInUnderlyingClusters(obj runtime.Object) ( glog.V(2).Infof("obj does not have %s finalizer. Nothing to do", FinalizerDeleteFromUnderlyingClusters) return obj, nil } - hasOrphanFinalizer := dh.hasFinalizerFunc(obj, metav1.FinalizerOrphan) + hasOrphanFinalizer := dh.hasFinalizerFunc(obj, metav1.FinalizerOrphanDependents) if hasOrphanFinalizer { glog.V(2).Infof("Found finalizer orphan. Nothing to do, just remove the finalizer") // If the obj has FinalizerOrphan finalizer, then we need to orphan the @@ -127,7 +127,7 @@ func (dh *DeletionHelper) HandleObjectInUnderlyingClusters(obj runtime.Object) ( if err != nil { return obj, err } - return dh.removeFinalizerFunc(obj, metav1.FinalizerOrphan) + return dh.removeFinalizerFunc(obj, metav1.FinalizerOrphanDependents) } glog.V(2).Infof("Deleting obj %s from underlying clusters", objName) diff --git a/pkg/api/helpers.go b/pkg/api/helpers.go index 6672ba2e288..970792fc74d 100644 --- a/pkg/api/helpers.go +++ b/pkg/api/helpers.go @@ -244,7 +244,7 @@ func IsServiceIPRequested(service *Service) bool { var standardFinalizers = sets.NewString( string(FinalizerKubernetes), - metav1.FinalizerOrphan, + metav1.FinalizerOrphanDependents, ) // HasAnnotation returns a bool if passed in annotation exists diff --git a/pkg/api/meta_test.go b/pkg/api/meta_test.go index ab355ecc891..f97758188ca 100644 --- a/pkg/api/meta_test.go +++ b/pkg/api/meta_test.go @@ -20,8 +20,6 @@ import ( "reflect" "testing" - "github.com/google/gofuzz" - "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -31,50 +29,6 @@ import ( var _ metav1.Object = &metav1.ObjectMeta{} -func getObjectMetaAndOwnerReferences() (objectMeta metav1.ObjectMeta, metaOwnerReferences []metav1.OwnerReference) { - fuzz.New().NilChance(.5).NumElements(1, 5).Fuzz(&objectMeta) - references := objectMeta.OwnerReferences - metaOwnerReferences = make([]metav1.OwnerReference, 0) - for i := 0; i < len(references); i++ { - metaOwnerReferences = append(metaOwnerReferences, metav1.OwnerReference{ - Kind: references[i].Kind, - Name: references[i].Name, - UID: references[i].UID, - APIVersion: references[i].APIVersion, - Controller: references[i].Controller, - }) - } - if len(references) == 0 { - objectMeta.OwnerReferences = make([]metav1.OwnerReference, 0) - } - return objectMeta, metaOwnerReferences -} - -func testGetOwnerReferences(t *testing.T) { - meta, expected := getObjectMetaAndOwnerReferences() - refs := meta.GetOwnerReferences() - if !reflect.DeepEqual(refs, expected) { - t.Errorf("expect %v\n got %v", expected, refs) - } -} - -func testSetOwnerReferences(t *testing.T) { - expected, newRefs := getObjectMetaAndOwnerReferences() - objectMeta := &metav1.ObjectMeta{} - objectMeta.SetOwnerReferences(newRefs) - if !reflect.DeepEqual(expected.OwnerReferences, objectMeta.OwnerReferences) { - t.Errorf("expect: %#v\n got: %#v", expected.OwnerReferences, objectMeta.OwnerReferences) - } -} - -func TestAccessOwnerReferences(t *testing.T) { - fuzzIter := 5 - for i := 0; i < fuzzIter; i++ { - testGetOwnerReferences(t) - testSetOwnerReferences(t) - } -} - func TestAccessorImplementations(t *testing.T) { for _, gv := range api.Registry.EnabledVersions() { internalGV := schema.GroupVersion{Group: gv.Group, Version: runtime.APIVersionInternal} diff --git a/pkg/api/types.go b/pkg/api/types.go index 427cc2e4019..70172c06cb3 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -3000,6 +3000,20 @@ type Preconditions struct { UID *types.UID } +// DeletionPropagation decides whether and how garbage collection will be performed. +type DeletionPropagation string + +const ( + // Orphans the dependents. + DeletePropagationOrphan DeletionPropagation = "Orphan" + // Deletes the object from the key-value store, the garbage collector will delete the dependents in the background. + DeletePropagationBackground DeletionPropagation = "Background" + // The object exists in the key-value store until the garbage collector deletes all the dependents whose ownerReference.blockOwnerDeletion=true from the key-value store. + // API sever will put the "DeletingDependents" finalizer on the object, and sets its deletionTimestamp. + // This policy is cascading, i.e., the dependents will be deleted with Foreground. + DeletePropagationForeground DeletionPropagation = "Foreground" +) + // DeleteOptions may be provided when deleting an API object // DEPRECATED: This type has been moved to meta/v1 and will be removed soon. type DeleteOptions struct { @@ -3016,10 +3030,18 @@ type DeleteOptions struct { // +optional Preconditions *Preconditions + // Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. // Should the dependent objects be orphaned. If true/false, the "orphan" // finalizer will be added to/removed from the object's finalizers list. + // Either this field or PropagationPolicy may be set, but not both. // +optional OrphanDependents *bool + + // Whether and how garbage collection will be performed. + // Defaults to Default. + // Either this field or OrphanDependents may be set, but not both. + // +optional + PropagationPolicy *DeletionPropagation } // ListOptions is the query options to a standard REST list call, and has future support for diff --git a/pkg/api/v1/helpers.go b/pkg/api/v1/helpers.go index 2bb238cacf6..b6443011628 100644 --- a/pkg/api/v1/helpers.go +++ b/pkg/api/v1/helpers.go @@ -84,7 +84,7 @@ func IsServiceIPRequested(service *Service) bool { var standardFinalizers = sets.NewString( string(FinalizerKubernetes), - metav1.FinalizerOrphan, + metav1.FinalizerOrphanDependents, ) func IsStandardFinalizerName(str string) bool { diff --git a/pkg/api/v1/meta.go b/pkg/api/v1/meta.go index fd30ef1f584..bb1ae2ff79d 100644 --- a/pkg/api/v1/meta.go +++ b/pkg/api/v1/meta.go @@ -63,6 +63,10 @@ func (meta *ObjectMeta) GetOwnerReferences() []metav1.OwnerReference { value := *meta.OwnerReferences[i].Controller ret[i].Controller = &value } + if meta.OwnerReferences[i].BlockOwnerDeletion != nil { + value := *meta.OwnerReferences[i].BlockOwnerDeletion + ret[i].BlockOwnerDeletion = &value + } } return ret } @@ -78,6 +82,10 @@ func (meta *ObjectMeta) SetOwnerReferences(references []metav1.OwnerReference) { value := *references[i].Controller newReferences[i].Controller = &value } + if references[i].BlockOwnerDeletion != nil { + value := *references[i].BlockOwnerDeletion + newReferences[i].BlockOwnerDeletion = &value + } } meta.OwnerReferences = newReferences } diff --git a/pkg/api/v1/types.go b/pkg/api/v1/types.go index 2b7b025e9b5..1961f367df8 100644 --- a/pkg/api/v1/types.go +++ b/pkg/api/v1/types.go @@ -3432,6 +3432,20 @@ type Preconditions struct { UID *types.UID `json:"uid,omitempty" protobuf:"bytes,1,opt,name=uid,casttype=k8s.io/apimachinery/pkg/types.UID"` } +// DeletionPropagation decides if a deletion will propagate to the dependents of the object, and how the garbage collector will handle the propagation. +type DeletionPropagation string + +const ( + // Orphans the dependents. + DeletePropagationOrphan DeletionPropagation = "Orphan" + // Deletes the object from the key-value store, the garbage collector will delete the dependents in the background. + DeletePropagationBackground DeletionPropagation = "Background" + // The object exists in the key-value store until the garbage collector deletes all the dependents whose ownerReference.blockOwnerDeletion=true from the key-value store. + // API sever will put the "DeletingDependents" finalizer on the object, and sets its deletionTimestamp. + // This policy is cascading, i.e., the dependents will be deleted with Foreground. + DeletePropagationForeground DeletionPropagation = "Foreground" +) + // DeleteOptions may be provided when deleting an API object // DEPRECATED: This type has been moved to meta/v1 and will be removed soon. // +k8s:openapi-gen=false @@ -3450,10 +3464,18 @@ type DeleteOptions struct { // +optional Preconditions *Preconditions `json:"preconditions,omitempty" protobuf:"bytes,2,opt,name=preconditions"` + // Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. // Should the dependent objects be orphaned. If true/false, the "orphan" // finalizer will be added to/removed from the object's finalizers list. + // Either this field or PropagationPolicy may be set, but not both. // +optional OrphanDependents *bool `json:"orphanDependents,omitempty" protobuf:"varint,3,opt,name=orphanDependents"` + + // Whether and how garbage collection will be performed. + // Defaults to Default. + // Either this field or OrphanDependents may be set, but not both. + // +optional + PropagationPolicy *DeletionPropagation } // ListOptions is the query options to a standard REST list call. diff --git a/pkg/api/validation/validation.go b/pkg/api/validation/validation.go index 8b002a4920e..0f8599313a7 100644 --- a/pkg/api/validation/validation.go +++ b/pkg/api/validation/validation.go @@ -295,7 +295,6 @@ func ValidateObjectMeta(meta *metav1.ObjectMeta, requiresNamespace bool, nameFn for i := range meta.Finalizers { allErrs = append(allErrs, validateKubeFinalizerName(string(meta.Finalizers[i]), fldPath.Child("finalizers").Index(i))...) } - return allErrs } diff --git a/pkg/apimachinery/tests/api_meta_meta_test.go b/pkg/apimachinery/tests/api_meta_meta_test.go index f0489fc04e1..fd20c842a7c 100644 --- a/pkg/apimachinery/tests/api_meta_meta_test.go +++ b/pkg/apimachinery/tests/api_meta_meta_test.go @@ -335,7 +335,7 @@ type MyAPIObject2 struct { metav1.ObjectMeta } -func getObjectMetaAndOwnerRefereneces() (myAPIObject2 MyAPIObject2, metaOwnerReferences []metav1.OwnerReference) { +func getObjectMetaAndOwnerReferences() (myAPIObject2 MyAPIObject2, metaOwnerReferences []metav1.OwnerReference) { fuzz.New().NilChance(.5).NumElements(1, 5).Fuzz(&myAPIObject2) references := myAPIObject2.ObjectMeta.OwnerReferences // This is necessary for the test to pass because the getter will return a @@ -343,11 +343,12 @@ func getObjectMetaAndOwnerRefereneces() (myAPIObject2 MyAPIObject2, metaOwnerRef metaOwnerReferences = make([]metav1.OwnerReference, 0) for i := 0; i < len(references); i++ { metaOwnerReferences = append(metaOwnerReferences, metav1.OwnerReference{ - Kind: references[i].Kind, - Name: references[i].Name, - UID: references[i].UID, - APIVersion: references[i].APIVersion, - Controller: references[i].Controller, + Kind: references[i].Kind, + Name: references[i].Name, + UID: references[i].UID, + APIVersion: references[i].APIVersion, + Controller: references[i].Controller, + BlockOwnerDeletion: references[i].BlockOwnerDeletion, }) } if len(references) == 0 { @@ -359,7 +360,7 @@ func getObjectMetaAndOwnerRefereneces() (myAPIObject2 MyAPIObject2, metaOwnerRef } func testGetOwnerReferences(t *testing.T) { - obj, expected := getObjectMetaAndOwnerRefereneces() + obj, expected := getObjectMetaAndOwnerReferences() accessor, err := meta.Accessor(&obj) if err != nil { t.Error(err) @@ -371,7 +372,7 @@ func testGetOwnerReferences(t *testing.T) { } func testSetOwnerReferences(t *testing.T) { - expected, references := getObjectMetaAndOwnerRefereneces() + expected, references := getObjectMetaAndOwnerReferences() obj := MyAPIObject2{} accessor, err := meta.Accessor(&obj) if err != nil { diff --git a/pkg/apimachinery/tests/apis_meta_v1_unstructed_unstructure_test.go b/pkg/apimachinery/tests/apis_meta_v1_unstructed_unstructure_test.go index ac74def8bf9..9121584b4c6 100644 --- a/pkg/apimachinery/tests/apis_meta_v1_unstructed_unstructure_test.go +++ b/pkg/apimachinery/tests/apis_meta_v1_unstructed_unstructure_test.go @@ -123,6 +123,7 @@ func TestDecode(t *testing.T) { } func TestUnstructuredGetters(t *testing.T) { + trueVar := true unstruct := unstructured.Unstructured{ Object: map[string]interface{}{ "kind": "test_kind", @@ -154,6 +155,10 @@ func TestUnstructuredGetters(t *testing.T) { "name": "podb", "apiVersion": "v1", "uid": "2", + // though these fields are of type *bool, but when + // decoded from JSON, they are unmarshalled as bool. + "controller": true, + "blockOwnerDeletion": true, }, }, "finalizers": []interface{}{ @@ -221,10 +226,12 @@ func TestUnstructuredGetters(t *testing.T) { UID: "1", }, { - Kind: "Pod", - Name: "podb", - APIVersion: "v1", - UID: "2", + Kind: "Pod", + Name: "podb", + APIVersion: "v1", + UID: "2", + Controller: &trueVar, + BlockOwnerDeletion: &trueVar, }, } if got, want := refs, expectedOwnerReferences; !reflect.DeepEqual(got, want) { @@ -263,18 +270,20 @@ func TestUnstructuredSetters(t *testing.T) { }, "ownerReferences": []map[string]interface{}{ { - "kind": "Pod", - "name": "poda", - "apiVersion": "v1", - "uid": "1", - "controller": (*bool)(nil), + "kind": "Pod", + "name": "poda", + "apiVersion": "v1", + "uid": "1", + "controller": (*bool)(nil), + "blockOwnerDeletion": (*bool)(nil), }, { - "kind": "Pod", - "name": "podb", - "apiVersion": "v1", - "uid": "2", - "controller": &trueVar, + "kind": "Pod", + "name": "podb", + "apiVersion": "v1", + "uid": "2", + "controller": &trueVar, + "blockOwnerDeletion": &trueVar, }, }, "finalizers": []interface{}{ @@ -307,11 +316,12 @@ func TestUnstructuredSetters(t *testing.T) { UID: "1", }, { - Kind: "Pod", - Name: "podb", - APIVersion: "v1", - UID: "2", - Controller: &trueVar, + Kind: "Pod", + Name: "podb", + APIVersion: "v1", + UID: "2", + Controller: &trueVar, + BlockOwnerDeletion: &trueVar, }, } unstruct.SetOwnerReferences(newOwnerReferences) diff --git a/pkg/registry/core/namespace/storage/storage.go b/pkg/registry/core/namespace/storage/storage.go index de7508ecfe9..269c94685b9 100644 --- a/pkg/registry/core/namespace/storage/storage.go +++ b/pkg/registry/core/namespace/storage/storage.go @@ -143,7 +143,7 @@ func (r *REST) Delete(ctx genericapirequest.Context, name string, options *metav newFinalizers := []string{} for i := range existingNamespace.ObjectMeta.Finalizers { finalizer := existingNamespace.ObjectMeta.Finalizers[i] - if string(finalizer) != metav1.FinalizerOrphan { + if string(finalizer) != metav1.FinalizerOrphanDependents { newFinalizers = append(newFinalizers, finalizer) } } diff --git a/staging/src/k8s.io/apimachinery/pkg/api/meta/meta.go b/staging/src/k8s.io/apimachinery/pkg/api/meta/meta.go index 4a958a92653..7492324ed95 100644 --- a/staging/src/k8s.io/apimachinery/pkg/api/meta/meta.go +++ b/staging/src/k8s.io/apimachinery/pkg/api/meta/meta.go @@ -334,6 +334,14 @@ func extractFromOwnerReference(v reflect.Value, o *metav1.OwnerReference) error controller := *controllerPtr o.Controller = &controller } + var blockOwnerDeletionPtr *bool + if err := runtime.Field(v, "BlockOwnerDeletion", &blockOwnerDeletionPtr); err != nil { + return err + } + if blockOwnerDeletionPtr != nil { + block := *blockOwnerDeletionPtr + o.BlockOwnerDeletion = &block + } return nil } @@ -357,6 +365,12 @@ func setOwnerReference(v reflect.Value, o *metav1.OwnerReference) error { return err } } + if o.BlockOwnerDeletion != nil { + block := *(o.BlockOwnerDeletion) + if err := runtime.SetField(&block, v, "BlockOwnerDeletion"); err != nil { + return err + } + } return nil } diff --git a/staging/src/k8s.io/apimachinery/pkg/api/validation/objectmeta.go b/staging/src/k8s.io/apimachinery/pkg/api/validation/objectmeta.go index f534dcc2a1a..0074e3c979b 100644 --- a/staging/src/k8s.io/apimachinery/pkg/api/validation/objectmeta.go +++ b/staging/src/k8s.io/apimachinery/pkg/api/validation/objectmeta.go @@ -171,8 +171,26 @@ func ValidateObjectMeta(meta *metav1.ObjectMeta, requiresNamespace bool, nameFn allErrs = append(allErrs, v1validation.ValidateLabels(meta.Labels, fldPath.Child("labels"))...) allErrs = append(allErrs, ValidateAnnotations(meta.Annotations, fldPath.Child("annotations"))...) allErrs = append(allErrs, ValidateOwnerReferences(meta.OwnerReferences, fldPath.Child("ownerReferences"))...) - for _, finalizer := range meta.Finalizers { - allErrs = append(allErrs, ValidateFinalizerName(finalizer, fldPath.Child("finalizers"))...) + allErrs = append(allErrs, ValidateFinalizers(meta.Finalizers, fldPath.Child("finalizers"))...) + return allErrs +} + +// ValidateFinalizers tests if the finalizers name are valid, and if there are conflicting finalizers. +func ValidateFinalizers(finalizers []string, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + hasFinalizerOrphanDependents := false + hasFinalizerDeleteDependents := false + for _, finalizer := range finalizers { + allErrs = append(allErrs, ValidateFinalizerName(finalizer, fldPath)...) + if finalizer == metav1.FinalizerOrphanDependents { + hasFinalizerOrphanDependents = true + } + if finalizer == metav1.FinalizerDeleteDependents { + hasFinalizerDeleteDependents = true + } + } + if hasFinalizerDeleteDependents && hasFinalizerOrphanDependents { + allErrs = append(allErrs, field.Invalid(fldPath, finalizers, fmt.Sprintf("finalizer %s and %s cannot be both set", metav1.FinalizerOrphanDependents, metav1.FinalizerDeleteDependents))) } return allErrs } diff --git a/staging/src/k8s.io/apimachinery/pkg/api/validation/objectmeta_test.go b/staging/src/k8s.io/apimachinery/pkg/api/validation/objectmeta_test.go index abb9af9d8ec..a8041d7fec1 100644 --- a/staging/src/k8s.io/apimachinery/pkg/api/validation/objectmeta_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/api/validation/objectmeta_test.go @@ -277,6 +277,28 @@ func TestValidateFinalizersUpdate(t *testing.T) { } } +func TestValidateFinalizersPreventConflictingFinalizers(t *testing.T) { + testcases := map[string]struct { + ObjectMeta metav1.ObjectMeta + ExpectedErr string + }{ + "conflicting finalizers": { + ObjectMeta: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", Finalizers: []string{metav1.FinalizerOrphanDependents, metav1.FinalizerDeleteDependents}}, + ExpectedErr: "cannot be both set", + }, + } + for name, tc := range testcases { + errs := ValidateObjectMeta(&tc.ObjectMeta, false, NameIsDNSSubdomain, field.NewPath("field")) + if len(errs) == 0 { + if len(tc.ExpectedErr) != 0 { + t.Errorf("case: %q, expected error to contain %q", name, tc.ExpectedErr) + } + } else if e, a := tc.ExpectedErr, errs.ToAggregate().Error(); !strings.Contains(a, e) { + t.Errorf("case: %q, expected error to contain %q, got error %q", name, e, a) + } + } +} + func TestValidateObjectMetaUpdatePreventsDeletionFieldMutation(t *testing.T) { now := metav1.NewTime(time.Unix(1000, 0).UTC()) later := metav1.NewTime(time.Unix(2000, 0).UTC()) diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/meta.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/meta.go index 871858ec273..108e34f0f59 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/meta.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/meta.go @@ -174,6 +174,10 @@ func (meta *ObjectMeta) GetOwnerReferences() []OwnerReference { value := *meta.OwnerReferences[i].Controller ret[i].Controller = &value } + if meta.OwnerReferences[i].BlockOwnerDeletion != nil { + value := *meta.OwnerReferences[i].BlockOwnerDeletion + ret[i].BlockOwnerDeletion = &value + } } return ret } @@ -189,6 +193,10 @@ func (meta *ObjectMeta) SetOwnerReferences(references []OwnerReference) { value := *references[i].Controller newReferences[i].Controller = &value } + if references[i].BlockOwnerDeletion != nil { + value := *references[i].BlockOwnerDeletion + newReferences[i].BlockOwnerDeletion = &value + } } meta.OwnerReferences = newReferences } diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/types.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/types.go index e9bade7690a..9a40b7e9adc 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/types.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/types.go @@ -73,7 +73,8 @@ type ListMeta struct { // These are internal finalizer values for Kubernetes-like APIs, must be qualified name unless defined here const ( - FinalizerOrphan string = "orphan" + FinalizerOrphanDependents string = "orphan" + FinalizerDeleteDependents string = "foregroundDeletion" ) // ObjectMeta is metadata that all persisted resources must have, which includes all objects @@ -255,6 +256,14 @@ type OwnerReference struct { // If true, this reference points to the managing controller. // +optional Controller *bool `json:"controller,omitempty" protobuf:"varint,6,opt,name=controller"` + // If true, AND if the owner has the "foregroundDeletion" finalizer, then + // the owner cannot be deleted from the key-value store until this + // reference is removed. + // Defaults to false. + // To set this field, a user needs "delete" permission of the owner, + // otherwise 422 (Unprocessable Entity) will be returned. + // +optional + BlockOwnerDeletion *bool `json:"blockOwnerDeletion,omitempty" protobuf:"varint,7,opt,name=blockOwnerDeletion"` } // ListOptions is the query options to a standard REST list call. @@ -305,6 +314,24 @@ type GetOptions struct { ResourceVersion string `json:"resourceVersion,omitempty" protobuf:"bytes,1,opt,name=resourceVersion"` } +// DeletionPropagation decides if a deletion will propagate to the dependents of +// the object, and how the garbage collector will handle the propagation. +type DeletionPropagation string + +const ( + // Orphans the dependents. + DeletePropagationOrphan DeletionPropagation = "Orphan" + // Deletes the object from the key-value store, the garbage collector will + // delete the dependents in the background. + DeletePropagationBackground DeletionPropagation = "Background" + // The object exists in the key-value store until the garbage collector + // deletes all the dependents whose ownerReference.blockOwnerDeletion=true + // from the key-value store. API sever will put the "foregroundDeletion" + // finalizer on the object, and sets its deletionTimestamp. This policy is + // cascading, i.e., the dependents will be deleted with Foreground. + DeletePropagationForeground DeletionPropagation = "Foreground" +) + // DeleteOptions may be provided when deleting an API object. type DeleteOptions struct { TypeMeta `json:",inline"` @@ -321,10 +348,18 @@ type DeleteOptions struct { // +optional Preconditions *Preconditions `json:"preconditions,omitempty" protobuf:"bytes,2,opt,name=preconditions"` + // Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. // Should the dependent objects be orphaned. If true/false, the "orphan" // finalizer will be added to/removed from the object's finalizers list. + // Either this field or PropagationPolicy may be set, but not both. // +optional OrphanDependents *bool `json:"orphanDependents,omitempty" protobuf:"varint,3,opt,name=orphanDependents"` + + // Whether and how garbage collection will be performed. + // Defaults to Default. + // Either this field or OrphanDependents may be set, but not both. + // +optional + PropagationPolicy *DeletionPropagation `json:"propagationPolicy,omitempty" protobuf:"varint,4,opt,name=propagationPolicy"` } // Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out. diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured.go index 318bea86d6a..ae20726b6ec 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured.go @@ -204,21 +204,31 @@ func (u *Unstructured) setNestedMap(value map[string]string, fields ...string) { func extractOwnerReference(src interface{}) metav1.OwnerReference { v := src.(map[string]interface{}) - controllerPtr, ok := (getNestedField(v, "controller")).(*bool) + // though this field is a *bool, but when decoded from JSON, it's + // unmarshalled as bool. + var controllerPtr *bool + controller, ok := (getNestedField(v, "controller")).(bool) if !ok { controllerPtr = nil } else { - if controllerPtr != nil { - controller := *controllerPtr - controllerPtr = &controller - } + controllerCopy := controller + controllerPtr = &controllerCopy + } + var blockOwnerDeletionPtr *bool + blockOwnerDeletion, ok := (getNestedField(v, "blockOwnerDeletion")).(bool) + if !ok { + blockOwnerDeletionPtr = nil + } else { + blockOwnerDeletionCopy := blockOwnerDeletion + blockOwnerDeletionPtr = &blockOwnerDeletionCopy } return metav1.OwnerReference{ - Kind: getNestedString(v, "kind"), - Name: getNestedString(v, "name"), - APIVersion: getNestedString(v, "apiVersion"), - UID: (types.UID)(getNestedString(v, "uid")), - Controller: controllerPtr, + Kind: getNestedString(v, "kind"), + Name: getNestedString(v, "name"), + APIVersion: getNestedString(v, "apiVersion"), + UID: (types.UID)(getNestedString(v, "uid")), + Controller: controllerPtr, + BlockOwnerDeletion: blockOwnerDeletionPtr, } } @@ -229,11 +239,17 @@ func setOwnerReference(src metav1.OwnerReference) map[string]interface{} { controller := *controllerPtr controllerPtr = &controller } + blockOwnerDeletionPtr := src.BlockOwnerDeletion + if blockOwnerDeletionPtr != nil { + blockOwnerDeletion := *blockOwnerDeletionPtr + blockOwnerDeletionPtr = &blockOwnerDeletion + } setNestedField(ret, src.Kind, "kind") setNestedField(ret, src.Name, "name") setNestedField(ret, src.APIVersion, "apiVersion") setNestedField(ret, string(src.UID), "uid") setNestedField(ret, controllerPtr, "controller") + setNestedField(ret, blockOwnerDeletionPtr, "blockOwnerDeletion") return ret } diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/validation/validation.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/validation/validation.go index 7d2d355a2ac..f06f075bf77 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/validation/validation.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/validation/validation.go @@ -17,6 +17,8 @@ limitations under the License. package validation import ( + "fmt" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/validation" "k8s.io/apimachinery/pkg/util/validation/field" @@ -72,3 +74,17 @@ func ValidateLabels(labels map[string]string, fldPath *field.Path) field.ErrorLi } return allErrs } + +func ValidateDeleteOptions(options *metav1.DeleteOptions) field.ErrorList { + allErrs := field.ErrorList{} + if options.OrphanDependents != nil && options.PropagationPolicy != nil { + allErrs = append(allErrs, field.Invalid(field.NewPath(""), options, "OrphanDependents and DeletionPropagation cannot be both set")) + } + if options.PropagationPolicy != nil && + *options.PropagationPolicy != metav1.DeletePropagationForeground && + *options.PropagationPolicy != metav1.DeletePropagationBackground && + *options.PropagationPolicy != metav1.DeletePropagationOrphan { + allErrs = append(allErrs, field.Invalid(field.NewPath(""), options, fmt.Sprintf("DeletionPropagation need to be one of %q, %q, %q or nil", metav1.DeletePropagationForeground, metav1.DeletePropagationBackground, metav1.DeletePropagationOrphan))) + } + return allErrs +} diff --git a/staging/src/k8s.io/client-go/pkg/api/helpers.go b/staging/src/k8s.io/client-go/pkg/api/helpers.go index 6672ba2e288..970792fc74d 100644 --- a/staging/src/k8s.io/client-go/pkg/api/helpers.go +++ b/staging/src/k8s.io/client-go/pkg/api/helpers.go @@ -244,7 +244,7 @@ func IsServiceIPRequested(service *Service) bool { var standardFinalizers = sets.NewString( string(FinalizerKubernetes), - metav1.FinalizerOrphan, + metav1.FinalizerOrphanDependents, ) // HasAnnotation returns a bool if passed in annotation exists diff --git a/staging/src/k8s.io/client-go/pkg/api/v1/helpers.go b/staging/src/k8s.io/client-go/pkg/api/v1/helpers.go index 8c804a6b890..05928dc009d 100644 --- a/staging/src/k8s.io/client-go/pkg/api/v1/helpers.go +++ b/staging/src/k8s.io/client-go/pkg/api/v1/helpers.go @@ -84,7 +84,7 @@ func IsServiceIPRequested(service *Service) bool { var standardFinalizers = sets.NewString( string(FinalizerKubernetes), - metav1.FinalizerOrphan, + metav1.FinalizerOrphanDependents, ) func IsStandardFinalizerName(str string) bool {