mirror of
https://github.com/optim-enterprises-bv/kubernetes.git
synced 2025-11-26 19:35:10 +00:00
Merge pull request #30838 from caesarxuchao/per-resource-orphan-behavior
Automatic merge from submit-queue [GarbageCollector] Allow per-resource default garbage collection behavior What's the bug: When deleting an RC with `deleteOptions.OrphanDependents==nil`, garbage collector is supposed to treat it as `deleteOptions.OrphanDependents==true", and orphan the pods created by it. But the apiserver is not doing that. What's in the pr: Allow each resource to specify the default garbage collection behavior in the registry. For example, RC registry's default GC behavior is Orphan, and Pod registry's default GC behavior is CascadingDeletion.
This commit is contained in:
@@ -24,6 +24,7 @@ import (
|
||||
"strconv"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/rest"
|
||||
"k8s.io/kubernetes/pkg/api/validation"
|
||||
"k8s.io/kubernetes/pkg/fields"
|
||||
"k8s.io/kubernetes/pkg/labels"
|
||||
@@ -41,6 +42,12 @@ type rcStrategy struct {
|
||||
// Strategy is the default logic that applies when creating and updating Replication Controller objects.
|
||||
var Strategy = rcStrategy{api.Scheme, api.SimpleNameGenerator}
|
||||
|
||||
// DefaultGarbageCollectionPolicy returns Orphan because that was the default
|
||||
// behavior before the server-side garbage collection was implemented.
|
||||
func (rcStrategy) DefaultGarbageCollectionPolicy() rest.GarbageCollectionPolicy {
|
||||
return rest.OrphanDependents
|
||||
}
|
||||
|
||||
// NamespaceScoped returns true because all Replication Controllers need to be within a namespace.
|
||||
func (rcStrategy) NamespaceScoped() bool {
|
||||
return true
|
||||
|
||||
@@ -445,29 +445,49 @@ var (
|
||||
errEmptiedFinalizers = fmt.Errorf("emptied finalizers")
|
||||
)
|
||||
|
||||
// return if we need to update the finalizers of the object, and the desired list of finalizers
|
||||
func shouldUpdateFinalizers(accessor meta.Object, options *api.DeleteOptions) (shouldUpdate bool, newFinalizers []string) {
|
||||
if options == nil || options.OrphanDependents == nil {
|
||||
return false, accessor.GetFinalizers()
|
||||
// shouldUpdateFinalizers returns if we need to update the finalizers of the
|
||||
// object, and the desired list of finalizers.
|
||||
// When deciding whether to add the OrphanDependent finalizer, factors in the
|
||||
// order of highest to lowest priority are: options.OrphanDependents, existing
|
||||
// finalizers of the object, e.DeleteStrategy.DefaultGarbageCollectionPolicy.
|
||||
func shouldUpdateFinalizers(e *Store, accessor meta.Object, options *api.DeleteOptions) (shouldUpdate bool, newFinalizers []string) {
|
||||
shouldOrphan := false
|
||||
// Get default orphan policy from this REST object type
|
||||
if gcStrategy, ok := e.DeleteStrategy.(rest.GarbageCollectionDeleteStrategy); ok {
|
||||
if gcStrategy.DefaultGarbageCollectionPolicy() == rest.OrphanDependents {
|
||||
shouldOrphan = true
|
||||
}
|
||||
}
|
||||
shouldOrphan := *options.OrphanDependents
|
||||
alreadyOrphan := false
|
||||
// If a finalizer is set in the object, it overrides the default
|
||||
hasOrphanFinalizer := false
|
||||
finalizers := accessor.GetFinalizers()
|
||||
newFinalizers = make([]string, 0, len(finalizers))
|
||||
for _, f := range finalizers {
|
||||
if f == api.FinalizerOrphan {
|
||||
alreadyOrphan = true
|
||||
if !shouldOrphan {
|
||||
shouldOrphan = true
|
||||
hasOrphanFinalizer = true
|
||||
break
|
||||
}
|
||||
// TODO: update this when we add a finalizer indicating a preference for the other behavior
|
||||
}
|
||||
// If an explicit policy was set at deletion time, that overrides both
|
||||
if options != nil && options.OrphanDependents != nil {
|
||||
shouldOrphan = *options.OrphanDependents
|
||||
}
|
||||
if shouldOrphan && !hasOrphanFinalizer {
|
||||
finalizers = append(finalizers, api.FinalizerOrphan)
|
||||
return true, finalizers
|
||||
}
|
||||
if !shouldOrphan && hasOrphanFinalizer {
|
||||
var newFinalizers []string
|
||||
for _, f := range finalizers {
|
||||
if f == api.FinalizerOrphan {
|
||||
continue
|
||||
}
|
||||
newFinalizers = append(newFinalizers, f)
|
||||
}
|
||||
newFinalizers = append(newFinalizers, f)
|
||||
return true, newFinalizers
|
||||
}
|
||||
if shouldOrphan && !alreadyOrphan {
|
||||
newFinalizers = append(newFinalizers, api.FinalizerOrphan)
|
||||
}
|
||||
shouldUpdate = shouldOrphan != alreadyOrphan
|
||||
return shouldUpdate, newFinalizers
|
||||
return false, finalizers
|
||||
}
|
||||
|
||||
// markAsDeleting sets the obj's DeletionGracePeriodSeconds to 0, and sets the
|
||||
@@ -560,7 +580,7 @@ func (e *Store) updateForGracefulDeletionAndFinalizers(ctx api.Context, name, ke
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
shouldUpdate, newFinalizers := shouldUpdateFinalizers(existingAccessor, options)
|
||||
shouldUpdate, newFinalizers := shouldUpdateFinalizers(e, existingAccessor, options)
|
||||
if shouldUpdate {
|
||||
existingAccessor.SetFinalizers(newFinalizers)
|
||||
}
|
||||
@@ -654,7 +674,7 @@ func (e *Store) Delete(ctx api.Context, name string, options *api.DeleteOptions)
|
||||
err, ignoreNotFound, deleteImmediately, out, lastExisting = e.updateForGracefulDeletion(ctx, name, key, options, preconditions, obj)
|
||||
}
|
||||
} else {
|
||||
shouldUpdateFinalizers, _ := shouldUpdateFinalizers(accessor, options)
|
||||
shouldUpdateFinalizers, _ := shouldUpdateFinalizers(e, accessor, options)
|
||||
// TODO: remove the check, because we support no-op updates now.
|
||||
if graceful || pendingFinalizers || shouldUpdateFinalizers {
|
||||
err, ignoreNotFound, deleteImmediately, out, lastExisting = e.updateForGracefulDeletionAndFinalizers(ctx, name, key, options, preconditions, obj)
|
||||
|
||||
@@ -46,6 +46,14 @@ import (
|
||||
"k8s.io/kubernetes/pkg/util/wait"
|
||||
)
|
||||
|
||||
type testOrphanDeleteStrategy struct {
|
||||
*testRESTStrategy
|
||||
}
|
||||
|
||||
func (t *testOrphanDeleteStrategy) DefaultGarbageCollectionPolicy() rest.GarbageCollectionPolicy {
|
||||
return rest.OrphanDependents
|
||||
}
|
||||
|
||||
type testRESTStrategy struct {
|
||||
runtime.ObjectTyper
|
||||
api.NameGenerator
|
||||
@@ -680,9 +688,16 @@ func TestStoreDeleteWithOrphanDependents(t *testing.T) {
|
||||
nonOrphanOptions := &api.DeleteOptions{OrphanDependents: &falseVar}
|
||||
nilOrphanOptions := &api.DeleteOptions{}
|
||||
|
||||
// defaultDeleteStrategy doesn't implement rest.GarbageCollectionDeleteStrategy.
|
||||
defaultDeleteStrategy := &testRESTStrategy{api.Scheme, api.SimpleNameGenerator, true, false, true}
|
||||
// orphanDeleteStrategy indicates the default garbage collection policy is
|
||||
// to orphan dependentes.
|
||||
orphanDeleteStrategy := &testOrphanDeleteStrategy{defaultDeleteStrategy}
|
||||
|
||||
testcases := []struct {
|
||||
pod *api.Pod
|
||||
options *api.DeleteOptions
|
||||
strategy rest.RESTDeleteStrategy
|
||||
expectNotFound bool
|
||||
updatedFinalizers []string
|
||||
}{
|
||||
@@ -690,99 +705,179 @@ func TestStoreDeleteWithOrphanDependents(t *testing.T) {
|
||||
{
|
||||
podWithOrphanFinalizer("pod1"),
|
||||
orphanOptions,
|
||||
defaultDeleteStrategy,
|
||||
false,
|
||||
[]string{"foo.com/x", api.FinalizerOrphan, "bar.com/y"},
|
||||
},
|
||||
{
|
||||
podWithOtherFinalizers("pod2"),
|
||||
orphanOptions,
|
||||
defaultDeleteStrategy,
|
||||
false,
|
||||
[]string{"foo.com/x", "bar.com/y", api.FinalizerOrphan},
|
||||
},
|
||||
{
|
||||
podWithNoFinalizer("pod3"),
|
||||
orphanOptions,
|
||||
defaultDeleteStrategy,
|
||||
false,
|
||||
[]string{api.FinalizerOrphan},
|
||||
},
|
||||
{
|
||||
podWithOnlyOrphanFinalizer("pod4"),
|
||||
orphanOptions,
|
||||
defaultDeleteStrategy,
|
||||
false,
|
||||
[]string{api.FinalizerOrphan},
|
||||
},
|
||||
// cases run with DeleteOptions.OrphanDedependents=false
|
||||
// these cases all have oprhanDeleteStrategy, which should be ignored
|
||||
// because DeleteOptions has the highest priority.
|
||||
{
|
||||
podWithOrphanFinalizer("pod5"),
|
||||
nonOrphanOptions,
|
||||
orphanDeleteStrategy,
|
||||
false,
|
||||
[]string{"foo.com/x", "bar.com/y"},
|
||||
},
|
||||
{
|
||||
podWithOtherFinalizers("pod6"),
|
||||
nonOrphanOptions,
|
||||
orphanDeleteStrategy,
|
||||
false,
|
||||
[]string{"foo.com/x", "bar.com/y"},
|
||||
},
|
||||
{
|
||||
podWithNoFinalizer("pod7"),
|
||||
nonOrphanOptions,
|
||||
orphanDeleteStrategy,
|
||||
true,
|
||||
[]string{},
|
||||
},
|
||||
{
|
||||
podWithOnlyOrphanFinalizer("pod8"),
|
||||
nonOrphanOptions,
|
||||
orphanDeleteStrategy,
|
||||
true,
|
||||
[]string{},
|
||||
},
|
||||
// cases run with nil DeleteOptions, the finalizers are not updated.
|
||||
// cases run with nil DeleteOptions.OrphanDependents. If the object
|
||||
// already has the orphan finalizer, then the DeleteStrategy should be
|
||||
// ignored. Otherwise the DeleteStrategy decides whether to add the
|
||||
// orphan finalizer.
|
||||
{
|
||||
podWithOrphanFinalizer("pod9"),
|
||||
nil,
|
||||
nilOrphanOptions,
|
||||
defaultDeleteStrategy,
|
||||
false,
|
||||
[]string{"foo.com/x", api.FinalizerOrphan, "bar.com/y"},
|
||||
},
|
||||
{
|
||||
podWithOtherFinalizers("pod10"),
|
||||
nil,
|
||||
podWithOrphanFinalizer("pod10"),
|
||||
nilOrphanOptions,
|
||||
orphanDeleteStrategy,
|
||||
false,
|
||||
[]string{"foo.com/x", api.FinalizerOrphan, "bar.com/y"},
|
||||
},
|
||||
{
|
||||
podWithOtherFinalizers("pod11"),
|
||||
nilOrphanOptions,
|
||||
defaultDeleteStrategy,
|
||||
false,
|
||||
[]string{"foo.com/x", "bar.com/y"},
|
||||
},
|
||||
{
|
||||
podWithNoFinalizer("pod11"),
|
||||
nil,
|
||||
podWithOtherFinalizers("pod12"),
|
||||
nilOrphanOptions,
|
||||
orphanDeleteStrategy,
|
||||
false,
|
||||
[]string{"foo.com/x", "bar.com/y", api.FinalizerOrphan},
|
||||
},
|
||||
{
|
||||
podWithNoFinalizer("pod13"),
|
||||
nilOrphanOptions,
|
||||
defaultDeleteStrategy,
|
||||
true,
|
||||
[]string{},
|
||||
},
|
||||
{
|
||||
podWithOnlyOrphanFinalizer("pod12"),
|
||||
nil,
|
||||
podWithNoFinalizer("pod14"),
|
||||
nilOrphanOptions,
|
||||
orphanDeleteStrategy,
|
||||
false,
|
||||
[]string{api.FinalizerOrphan},
|
||||
},
|
||||
// cases run with non-nil DeleteOptions, but nil OrphanDependents, it's treated as OrphanDependents=true
|
||||
{
|
||||
podWithOrphanFinalizer("pod13"),
|
||||
podWithOnlyOrphanFinalizer("pod15"),
|
||||
nilOrphanOptions,
|
||||
defaultDeleteStrategy,
|
||||
false,
|
||||
[]string{"foo.com/x", api.FinalizerOrphan, "bar.com/y"},
|
||||
},
|
||||
{
|
||||
podWithOtherFinalizers("pod14"),
|
||||
nilOrphanOptions,
|
||||
false,
|
||||
[]string{"foo.com/x", "bar.com/y"},
|
||||
},
|
||||
{
|
||||
podWithNoFinalizer("pod15"),
|
||||
nilOrphanOptions,
|
||||
true,
|
||||
[]string{},
|
||||
[]string{api.FinalizerOrphan},
|
||||
},
|
||||
{
|
||||
podWithOnlyOrphanFinalizer("pod16"),
|
||||
nilOrphanOptions,
|
||||
orphanDeleteStrategy,
|
||||
false,
|
||||
[]string{api.FinalizerOrphan},
|
||||
},
|
||||
|
||||
// cases run with nil DeleteOptions should have exact same behavior.
|
||||
// They should be exactly the same as above cases where
|
||||
// DeleteOptions.OrphanDependents is nil.
|
||||
{
|
||||
podWithOrphanFinalizer("pod17"),
|
||||
nil,
|
||||
defaultDeleteStrategy,
|
||||
false,
|
||||
[]string{"foo.com/x", api.FinalizerOrphan, "bar.com/y"},
|
||||
},
|
||||
{
|
||||
podWithOrphanFinalizer("pod18"),
|
||||
nil,
|
||||
orphanDeleteStrategy,
|
||||
false,
|
||||
[]string{"foo.com/x", api.FinalizerOrphan, "bar.com/y"},
|
||||
},
|
||||
{
|
||||
podWithOtherFinalizers("pod19"),
|
||||
nil,
|
||||
defaultDeleteStrategy,
|
||||
false,
|
||||
[]string{"foo.com/x", "bar.com/y"},
|
||||
},
|
||||
{
|
||||
podWithOtherFinalizers("pod20"),
|
||||
nil,
|
||||
orphanDeleteStrategy,
|
||||
false,
|
||||
[]string{"foo.com/x", "bar.com/y", api.FinalizerOrphan},
|
||||
},
|
||||
{
|
||||
podWithNoFinalizer("pod21"),
|
||||
nil,
|
||||
defaultDeleteStrategy,
|
||||
true,
|
||||
[]string{},
|
||||
},
|
||||
{
|
||||
podWithNoFinalizer("pod22"),
|
||||
nil,
|
||||
orphanDeleteStrategy,
|
||||
false,
|
||||
[]string{api.FinalizerOrphan},
|
||||
},
|
||||
{
|
||||
podWithOnlyOrphanFinalizer("pod23"),
|
||||
nil,
|
||||
defaultDeleteStrategy,
|
||||
false,
|
||||
[]string{api.FinalizerOrphan},
|
||||
},
|
||||
{
|
||||
podWithOnlyOrphanFinalizer("pod24"),
|
||||
nil,
|
||||
orphanDeleteStrategy,
|
||||
false,
|
||||
[]string{api.FinalizerOrphan},
|
||||
},
|
||||
@@ -793,6 +888,7 @@ func TestStoreDeleteWithOrphanDependents(t *testing.T) {
|
||||
defer server.Terminate(t)
|
||||
|
||||
for _, tc := range testcases {
|
||||
registry.DeleteStrategy = tc.strategy
|
||||
// create pod
|
||||
_, err := registry.Create(testContext, tc.pod)
|
||||
if err != nil {
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"strconv"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/rest"
|
||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||
"k8s.io/kubernetes/pkg/apis/extensions/validation"
|
||||
"k8s.io/kubernetes/pkg/fields"
|
||||
@@ -42,6 +43,12 @@ type rsStrategy struct {
|
||||
// Strategy is the default logic that applies when creating and updating ReplicaSet objects.
|
||||
var Strategy = rsStrategy{api.Scheme, api.SimpleNameGenerator}
|
||||
|
||||
// DefaultGarbageCollectionPolicy returns Orphan because that's the default
|
||||
// behavior before the server-side garbage collection is implemented.
|
||||
func (rsStrategy) DefaultGarbageCollectionPolicy() rest.GarbageCollectionPolicy {
|
||||
return rest.OrphanDependents
|
||||
}
|
||||
|
||||
// NamespaceScoped returns true because all ReplicaSets need to be within a namespace.
|
||||
func (rsStrategy) NamespaceScoped() bool {
|
||||
return true
|
||||
|
||||
Reference in New Issue
Block a user