Merge pull request #55148 from dixudx/controller_defaultGC_DeleteDependents

Automatic merge from submit-queue (batch tested with PRs 52767, 55065, 55148, 56228, 56221). If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>.

change DefaultGarbageCollectionPolicy to DeleteDependents for workloads controllers

**What this PR does / why we need it**:
As part of the apps/v1 GA effort (kubernetes/features#353) for v1.9. For core controllers, like `Deployment`, `DaemonSet`, `ReplicaSet`, and `StatefulSet`, changing the `DefaultGarbageCollectionPolicy` from `OrphanDependents` to `DeleteDependents` will make these objects consistent with the default behavior for all new objects.

For legacy API versions, the `DefaultGarbageCollectionPolicy` remains `OrphanDependents`.

**Which issue(s) this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close the issue(s) when PR gets merged)*:
ref #55027

**Special notes for your reviewer**:
/cc @enisoc @caesarxuchao @kow3ns
/assign @kubernetes/sig-apps-api-reviews 

**Release note**:

```release-note
The default garbage collection policy for Deployment, DaemonSet, StatefulSet, and ReplicaSet has changed from OrphanDependents to DeleteDependents when the deletion is requested through an `apps/v1` endpoint. Clients using older endpoints will be unaffected. This change is only at the REST API level and is independent of the default behavior of particular clients (e.g. this does not affect the default for the kubectl `--cascade` flag).

If you upgrade your client-go libs and use the `AppsV1()` interface, please note that the default garbage collection behavior is changed.
```
pull/6/head
Kubernetes Submit Queue 2017-11-22 19:49:37 -08:00 committed by GitHub
commit 00b2d95c86
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 513 additions and 43 deletions

View File

@ -19,11 +19,14 @@ go_library(
"//pkg/api/pod:go_default_library", "//pkg/api/pod:go_default_library",
"//pkg/apis/apps:go_default_library", "//pkg/apis/apps:go_default_library",
"//pkg/apis/apps/validation:go_default_library", "//pkg/apis/apps/validation:go_default_library",
"//vendor/k8s.io/api/apps/v1beta1:go_default_library",
"//vendor/k8s.io/api/apps/v1beta2:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/internalversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/internalversion:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library",
"//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library", "//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library",

View File

@ -17,8 +17,11 @@ limitations under the License.
package statefulset package statefulset
import ( import (
appsv1beta1 "k8s.io/api/apps/v1beta1"
appsv1beta2 "k8s.io/api/apps/v1beta2"
apiequality "k8s.io/apimachinery/pkg/api/equality" apiequality "k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apimachinery/pkg/util/validation/field"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request" genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/registry/rest" "k8s.io/apiserver/pkg/registry/rest"
@ -38,9 +41,18 @@ type statefulSetStrategy struct {
// Strategy is the default logic that applies when creating and updating Replication StatefulSet objects. // Strategy is the default logic that applies when creating and updating Replication StatefulSet objects.
var Strategy = statefulSetStrategy{legacyscheme.Scheme, names.SimpleNameGenerator} var Strategy = statefulSetStrategy{legacyscheme.Scheme, names.SimpleNameGenerator}
// DefaultGarbageCollectionPolicy returns Orphan because that was the default // DefaultGarbageCollectionPolicy returns OrphanDependents by default. For apps/v1, returns DeleteDependents.
// behavior before the server-side garbage collection was implemented. func (statefulSetStrategy) DefaultGarbageCollectionPolicy(ctx genericapirequest.Context) rest.GarbageCollectionPolicy {
func (statefulSetStrategy) DefaultGarbageCollectionPolicy() rest.GarbageCollectionPolicy { if requestInfo, found := genericapirequest.RequestInfoFrom(ctx); found {
groupVersion := schema.GroupVersion{Group: requestInfo.APIGroup, Version: requestInfo.APIVersion}
switch groupVersion {
case appsv1beta1.SchemeGroupVersion, appsv1beta2.SchemeGroupVersion:
// for back compatibility
return rest.OrphanDependents
default:
return rest.DeleteDependents
}
}
return rest.OrphanDependents return rest.OrphanDependents
} }

View File

@ -91,12 +91,59 @@ func TestStatefulSetStrategy(t *testing.T) {
if len(errs) == 0 { if len(errs) == 0 {
t.Errorf("expected a validation error since updates are disallowed on statefulsets.") t.Errorf("expected a validation error since updates are disallowed on statefulsets.")
} }
}
func TestStatefulsetDefaultGarbageCollectionPolicy(t *testing.T) {
// Make sure we correctly implement the interface. // Make sure we correctly implement the interface.
// Otherwise a typo could silently change the default. // Otherwise a typo could silently change the default.
var gcds rest.GarbageCollectionDeleteStrategy = Strategy var gcds rest.GarbageCollectionDeleteStrategy = Strategy
if got, want := gcds.DefaultGarbageCollectionPolicy(), rest.OrphanDependents; got != want { tests := []struct {
t.Errorf("DefaultGarbageCollectionPolicy() = %#v, want %#v", got, want) requestInfo genericapirequest.RequestInfo
expectedGCPolicy rest.GarbageCollectionPolicy
isNilRequestInfo bool
}{
{
genericapirequest.RequestInfo{
APIGroup: "apps",
APIVersion: "v1beta1",
Resource: "statefulsets",
},
rest.OrphanDependents,
false,
},
{
genericapirequest.RequestInfo{
APIGroup: "apps",
APIVersion: "v1beta2",
Resource: "statefulsets",
},
rest.OrphanDependents,
false,
},
{
genericapirequest.RequestInfo{
APIGroup: "apps",
APIVersion: "v1",
Resource: "statefulsets",
},
rest.DeleteDependents,
false,
},
{
expectedGCPolicy: rest.OrphanDependents,
isNilRequestInfo: true,
},
}
for _, test := range tests {
context := genericapirequest.NewContext()
if !test.isNilRequestInfo {
context = genericapirequest.WithRequestInfo(context, &test.requestInfo)
}
if got, want := gcds.DefaultGarbageCollectionPolicy(context), test.expectedGCPolicy; got != want {
t.Errorf("%s/%s: DefaultGarbageCollectionPolicy() = %#v, want %#v", test.requestInfo.APIGroup,
test.requestInfo.APIVersion, got, want)
}
} }
} }

View File

@ -39,7 +39,7 @@ var Strategy = cronJobStrategy{legacyscheme.Scheme, names.SimpleNameGenerator}
// DefaultGarbageCollectionPolicy returns Orphan because that was the default // DefaultGarbageCollectionPolicy returns Orphan because that was the default
// behavior before the server-side garbage collection was implemented. // behavior before the server-side garbage collection was implemented.
func (cronJobStrategy) DefaultGarbageCollectionPolicy() rest.GarbageCollectionPolicy { func (cronJobStrategy) DefaultGarbageCollectionPolicy(ctx genericapirequest.Context) rest.GarbageCollectionPolicy {
return rest.OrphanDependents return rest.OrphanDependents
} }

View File

@ -96,7 +96,7 @@ func TestCronJobStrategy(t *testing.T) {
// Make sure we correctly implement the interface. // Make sure we correctly implement the interface.
// Otherwise a typo could silently change the default. // Otherwise a typo could silently change the default.
var gcds rest.GarbageCollectionDeleteStrategy = Strategy var gcds rest.GarbageCollectionDeleteStrategy = Strategy
if got, want := gcds.DefaultGarbageCollectionPolicy(), rest.OrphanDependents; got != want { if got, want := gcds.DefaultGarbageCollectionPolicy(genericapirequest.NewContext()), rest.OrphanDependents; got != want {
t.Errorf("DefaultGarbageCollectionPolicy() = %#v, want %#v", got, want) t.Errorf("DefaultGarbageCollectionPolicy() = %#v, want %#v", got, want)
} }
} }

View File

@ -47,7 +47,7 @@ var Strategy = jobStrategy{legacyscheme.Scheme, names.SimpleNameGenerator}
// DefaultGarbageCollectionPolicy returns Orphan because that was the default // DefaultGarbageCollectionPolicy returns Orphan because that was the default
// behavior before the server-side garbage collection was implemented. // behavior before the server-side garbage collection was implemented.
func (jobStrategy) DefaultGarbageCollectionPolicy() rest.GarbageCollectionPolicy { func (jobStrategy) DefaultGarbageCollectionPolicy(ctx genericapirequest.Context) rest.GarbageCollectionPolicy {
return rest.OrphanDependents return rest.OrphanDependents
} }

View File

@ -105,7 +105,7 @@ func TestJobStrategy(t *testing.T) {
// Make sure we correctly implement the interface. // Make sure we correctly implement the interface.
// Otherwise a typo could silently change the default. // Otherwise a typo could silently change the default.
var gcds rest.GarbageCollectionDeleteStrategy = Strategy var gcds rest.GarbageCollectionDeleteStrategy = Strategy
if got, want := gcds.DefaultGarbageCollectionPolicy(), rest.OrphanDependents; got != want { if got, want := gcds.DefaultGarbageCollectionPolicy(genericapirequest.NewContext()), rest.OrphanDependents; got != want {
t.Errorf("DefaultGarbageCollectionPolicy() = %#v, want %#v", got, want) t.Errorf("DefaultGarbageCollectionPolicy() = %#v, want %#v", got, want)
} }
} }

View File

@ -42,7 +42,7 @@ type eventStrategy struct {
// Event objects via the REST API. // Event objects via the REST API.
var Strategy = eventStrategy{legacyscheme.Scheme, names.SimpleNameGenerator} var Strategy = eventStrategy{legacyscheme.Scheme, names.SimpleNameGenerator}
func (eventStrategy) DefaultGarbageCollectionPolicy() rest.GarbageCollectionPolicy { func (eventStrategy) DefaultGarbageCollectionPolicy(ctx genericapirequest.Context) rest.GarbageCollectionPolicy {
return rest.Unsupported return rest.Unsupported
} }

View File

@ -51,7 +51,7 @@ var Strategy = rcStrategy{legacyscheme.Scheme, names.SimpleNameGenerator}
// DefaultGarbageCollectionPolicy returns Orphan because that was the default // DefaultGarbageCollectionPolicy returns Orphan because that was the default
// behavior before the server-side garbage collection was implemented. // behavior before the server-side garbage collection was implemented.
func (rcStrategy) DefaultGarbageCollectionPolicy() rest.GarbageCollectionPolicy { func (rcStrategy) DefaultGarbageCollectionPolicy(ctx genericapirequest.Context) rest.GarbageCollectionPolicy {
return rest.OrphanDependents return rest.OrphanDependents
} }

View File

@ -18,6 +18,7 @@ go_library(
"//pkg/api/pod:go_default_library", "//pkg/api/pod:go_default_library",
"//pkg/apis/extensions:go_default_library", "//pkg/apis/extensions:go_default_library",
"//pkg/apis/extensions/validation:go_default_library", "//pkg/apis/extensions/validation:go_default_library",
"//vendor/k8s.io/api/apps/v1beta2:go_default_library",
"//vendor/k8s.io/api/extensions/v1beta1:go_default_library", "//vendor/k8s.io/api/extensions/v1beta1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/validation:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/validation:go_default_library",

View File

@ -17,6 +17,7 @@ limitations under the License.
package daemonset package daemonset
import ( import (
appsv1beta2 "k8s.io/api/apps/v1beta2"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1" extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
apiequality "k8s.io/apimachinery/pkg/api/equality" apiequality "k8s.io/apimachinery/pkg/api/equality"
apivalidation "k8s.io/apimachinery/pkg/api/validation" apivalidation "k8s.io/apimachinery/pkg/api/validation"
@ -41,9 +42,18 @@ type daemonSetStrategy struct {
// Strategy is the default logic that applies when creating and updating DaemonSet objects. // Strategy is the default logic that applies when creating and updating DaemonSet objects.
var Strategy = daemonSetStrategy{legacyscheme.Scheme, names.SimpleNameGenerator} var Strategy = daemonSetStrategy{legacyscheme.Scheme, names.SimpleNameGenerator}
// DefaultGarbageCollectionPolicy returns Orphan because that was the default // DefaultGarbageCollectionPolicy returns OrphanDependents by default. For apps/v1, returns DeleteDependents.
// behavior before the server-side garbage collection was implemented. func (daemonSetStrategy) DefaultGarbageCollectionPolicy(ctx genericapirequest.Context) rest.GarbageCollectionPolicy {
func (daemonSetStrategy) DefaultGarbageCollectionPolicy() rest.GarbageCollectionPolicy { if requestInfo, found := genericapirequest.RequestInfoFrom(ctx); found {
groupVersion := schema.GroupVersion{Group: requestInfo.APIGroup, Version: requestInfo.APIVersion}
switch groupVersion {
case extensionsv1beta1.SchemeGroupVersion, appsv1beta2.SchemeGroupVersion:
// for back compatibility
return rest.OrphanDependents
default:
return rest.DeleteDependents
}
}
return rest.OrphanDependents return rest.OrphanDependents
} }

View File

@ -35,12 +35,57 @@ const (
namespace = "test-namespace" namespace = "test-namespace"
) )
func TestDefaultGarbageCollectionPolicy(t *testing.T) { func TestDaemonsetDefaultGarbageCollectionPolicy(t *testing.T) {
// Make sure we correctly implement the interface. // Make sure we correctly implement the interface.
// Otherwise a typo could silently change the default. // Otherwise a typo could silently change the default.
var gcds rest.GarbageCollectionDeleteStrategy = Strategy var gcds rest.GarbageCollectionDeleteStrategy = Strategy
if got, want := gcds.DefaultGarbageCollectionPolicy(), rest.OrphanDependents; got != want { tests := []struct {
t.Errorf("DefaultGarbageCollectionPolicy() = %#v, want %#v", got, want) requestInfo genericapirequest.RequestInfo
expectedGCPolicy rest.GarbageCollectionPolicy
isNilRequestInfo bool
}{
{
genericapirequest.RequestInfo{
APIGroup: "extensions",
APIVersion: "v1beta1",
Resource: "daemonsets",
},
rest.OrphanDependents,
false,
},
{
genericapirequest.RequestInfo{
APIGroup: "apps",
APIVersion: "v1beta2",
Resource: "daemonsets",
},
rest.OrphanDependents,
false,
},
{
genericapirequest.RequestInfo{
APIGroup: "apps",
APIVersion: "v1",
Resource: "daemonsets",
},
rest.DeleteDependents,
false,
},
{
expectedGCPolicy: rest.OrphanDependents,
isNilRequestInfo: true,
},
}
for _, test := range tests {
context := genericapirequest.NewContext()
if !test.isNilRequestInfo {
context = genericapirequest.WithRequestInfo(context, &test.requestInfo)
}
if got, want := gcds.DefaultGarbageCollectionPolicy(context), test.expectedGCPolicy; got != want {
t.Errorf("%s/%s: DefaultGarbageCollectionPolicy() = %#v, want %#v", test.requestInfo.APIGroup,
test.requestInfo.APIVersion, got, want)
}
} }
} }

View File

@ -20,6 +20,7 @@ go_library(
"//pkg/apis/extensions:go_default_library", "//pkg/apis/extensions:go_default_library",
"//pkg/apis/extensions/validation:go_default_library", "//pkg/apis/extensions/validation:go_default_library",
"//vendor/k8s.io/api/apps/v1beta1:go_default_library", "//vendor/k8s.io/api/apps/v1beta1:go_default_library",
"//vendor/k8s.io/api/apps/v1beta2:go_default_library",
"//vendor/k8s.io/api/extensions/v1beta1:go_default_library", "//vendor/k8s.io/api/extensions/v1beta1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/validation:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/validation:go_default_library",
@ -47,6 +48,7 @@ go_test(
"//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
"//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library", "//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
"//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library",
], ],
) )

View File

@ -18,6 +18,7 @@ package deployment
import ( import (
appsv1beta1 "k8s.io/api/apps/v1beta1" appsv1beta1 "k8s.io/api/apps/v1beta1"
appsv1beta2 "k8s.io/api/apps/v1beta2"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1" extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
apiequality "k8s.io/apimachinery/pkg/api/equality" apiequality "k8s.io/apimachinery/pkg/api/equality"
apivalidation "k8s.io/apimachinery/pkg/api/validation" apivalidation "k8s.io/apimachinery/pkg/api/validation"
@ -43,9 +44,18 @@ type deploymentStrategy struct {
// objects via the REST API. // objects via the REST API.
var Strategy = deploymentStrategy{legacyscheme.Scheme, names.SimpleNameGenerator} var Strategy = deploymentStrategy{legacyscheme.Scheme, names.SimpleNameGenerator}
// DefaultGarbageCollectionPolicy returns Orphan because that's the default // DefaultGarbageCollectionPolicy returns OrphanDependents by default. For apps/v1, returns DeleteDependents.
// behavior before the server-side garbage collection is implemented. func (deploymentStrategy) DefaultGarbageCollectionPolicy(ctx genericapirequest.Context) rest.GarbageCollectionPolicy {
func (deploymentStrategy) DefaultGarbageCollectionPolicy() rest.GarbageCollectionPolicy { if requestInfo, found := genericapirequest.RequestInfoFrom(ctx); found {
groupVersion := schema.GroupVersion{Group: requestInfo.APIGroup, Version: requestInfo.APIVersion}
switch groupVersion {
case extensionsv1beta1.SchemeGroupVersion, appsv1beta1.SchemeGroupVersion, appsv1beta2.SchemeGroupVersion:
// for back compatibility
return rest.OrphanDependents
default:
return rest.DeleteDependents
}
}
return rest.OrphanDependents return rest.OrphanDependents
} }

View File

@ -25,6 +25,7 @@ import (
"k8s.io/apimachinery/pkg/util/intstr" "k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apimachinery/pkg/util/validation/field"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request" genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/registry/rest"
api "k8s.io/kubernetes/pkg/apis/core" api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/apis/extensions"
) )
@ -183,3 +184,66 @@ func newDeploymentWithSelectorLabels(selectorLabels map[string]string) *extensio
}, },
} }
} }
func TestDeploymentDefaultGarbageCollectionPolicy(t *testing.T) {
// Make sure we correctly implement the interface.
// Otherwise a typo could silently change the default.
var gcds rest.GarbageCollectionDeleteStrategy = Strategy
tests := []struct {
requestInfo genericapirequest.RequestInfo
expectedGCPolicy rest.GarbageCollectionPolicy
isNilRequestInfo bool
}{
{
genericapirequest.RequestInfo{
APIGroup: "extensions",
APIVersion: "v1beta1",
Resource: "deployments",
},
rest.OrphanDependents,
false,
},
{
genericapirequest.RequestInfo{
APIGroup: "apps",
APIVersion: "v1beta1",
Resource: "deployments",
},
rest.OrphanDependents,
false,
},
{
genericapirequest.RequestInfo{
APIGroup: "apps",
APIVersion: "v1beta2",
Resource: "deployments",
},
rest.OrphanDependents,
false,
},
{
genericapirequest.RequestInfo{
APIGroup: "apps",
APIVersion: "v1",
Resource: "deployments",
},
rest.DeleteDependents,
false,
},
{
expectedGCPolicy: rest.OrphanDependents,
isNilRequestInfo: true,
},
}
for _, test := range tests {
context := genericapirequest.NewContext()
if !test.isNilRequestInfo {
context = genericapirequest.WithRequestInfo(context, &test.requestInfo)
}
if got, want := gcds.DefaultGarbageCollectionPolicy(context), test.expectedGCPolicy; got != want {
t.Errorf("%s/%s: DefaultGarbageCollectionPolicy() = %#v, want %#v", test.requestInfo.APIGroup,
test.requestInfo.APIVersion, got, want)
}
}
}

View File

@ -19,6 +19,7 @@ go_library(
"//pkg/api/pod:go_default_library", "//pkg/api/pod:go_default_library",
"//pkg/apis/extensions:go_default_library", "//pkg/apis/extensions:go_default_library",
"//pkg/apis/extensions/validation:go_default_library", "//pkg/apis/extensions/validation:go_default_library",
"//vendor/k8s.io/api/apps/v1beta2:go_default_library",
"//vendor/k8s.io/api/extensions/v1beta1:go_default_library", "//vendor/k8s.io/api/extensions/v1beta1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/validation:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/validation:go_default_library",
@ -49,6 +50,7 @@ go_test(
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
"//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library", "//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
"//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library",
], ],
) )

View File

@ -22,6 +22,7 @@ import (
"fmt" "fmt"
"strconv" "strconv"
appsv1beta2 "k8s.io/api/apps/v1beta2"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1" extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
apiequality "k8s.io/apimachinery/pkg/api/equality" apiequality "k8s.io/apimachinery/pkg/api/equality"
apivalidation "k8s.io/apimachinery/pkg/api/validation" apivalidation "k8s.io/apimachinery/pkg/api/validation"
@ -50,9 +51,18 @@ type rsStrategy struct {
// Strategy is the default logic that applies when creating and updating ReplicaSet objects. // Strategy is the default logic that applies when creating and updating ReplicaSet objects.
var Strategy = rsStrategy{legacyscheme.Scheme, names.SimpleNameGenerator} var Strategy = rsStrategy{legacyscheme.Scheme, names.SimpleNameGenerator}
// DefaultGarbageCollectionPolicy returns Orphan because that's the default // DefaultGarbageCollectionPolicy returns OrphanDependents by default. For apps/v1, returns DeleteDependents.
// behavior before the server-side garbage collection is implemented. func (rsStrategy) DefaultGarbageCollectionPolicy(ctx genericapirequest.Context) rest.GarbageCollectionPolicy {
func (rsStrategy) DefaultGarbageCollectionPolicy() rest.GarbageCollectionPolicy { if requestInfo, found := genericapirequest.RequestInfoFrom(ctx); found {
groupVersion := schema.GroupVersion{Group: requestInfo.APIGroup, Version: requestInfo.APIVersion}
switch groupVersion {
case extensionsv1beta1.SchemeGroupVersion, appsv1beta2.SchemeGroupVersion:
// for back compatibility
return rest.OrphanDependents
default:
return rest.DeleteDependents
}
}
return rest.OrphanDependents return rest.OrphanDependents
} }

View File

@ -23,6 +23,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apimachinery/pkg/util/validation/field"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request" genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/registry/rest"
api "k8s.io/kubernetes/pkg/apis/core" api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/apis/extensions"
) )
@ -227,3 +228,57 @@ func newReplicaSetWithSelectorLabels(selectorLabels map[string]string) *extensio
}, },
} }
} }
func TestReplicasetDefaultGarbageCollectionPolicy(t *testing.T) {
// Make sure we correctly implement the interface.
// Otherwise a typo could silently change the default.
var gcds rest.GarbageCollectionDeleteStrategy = Strategy
tests := []struct {
requestInfo genericapirequest.RequestInfo
expectedGCPolicy rest.GarbageCollectionPolicy
isNilRequestInfo bool
}{
{
genericapirequest.RequestInfo{
APIGroup: "extensions",
APIVersion: "v1beta1",
Resource: "replicasets",
},
rest.OrphanDependents,
false,
},
{
genericapirequest.RequestInfo{
APIGroup: "apps",
APIVersion: "v1beta2",
Resource: "replicasets",
},
rest.OrphanDependents,
false,
},
{
genericapirequest.RequestInfo{
APIGroup: "apps",
APIVersion: "v1",
Resource: "replicasets",
},
rest.DeleteDependents,
false,
},
{
expectedGCPolicy: rest.OrphanDependents,
isNilRequestInfo: true,
},
}
for _, test := range tests {
context := genericapirequest.NewContext()
if !test.isNilRequestInfo {
context = genericapirequest.WithRequestInfo(context, &test.requestInfo)
}
if got, want := gcds.DefaultGarbageCollectionPolicy(context), test.expectedGCPolicy; got != want {
t.Errorf("%s/%s: DefaultGarbageCollectionPolicy() = %#v, want %#v", test.requestInfo.APIGroup,
test.requestInfo.APIVersion, got, want)
}
}
}

View File

@ -24,6 +24,7 @@ go_test(
"//vendor/k8s.io/apimachinery/pkg/fields:go_default_library", "//vendor/k8s.io/apimachinery/pkg/fields:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/selection:go_default_library", "//vendor/k8s.io/apimachinery/pkg/selection:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",

View File

@ -699,12 +699,17 @@ var (
// priority, there are three factors affect whether to add/remove the // priority, there are three factors affect whether to add/remove the
// FinalizerOrphanDependents: options, existing finalizers of the object, // FinalizerOrphanDependents: options, existing finalizers of the object,
// and e.DeleteStrategy.DefaultGarbageCollectionPolicy. // and e.DeleteStrategy.DefaultGarbageCollectionPolicy.
func shouldOrphanDependents(e *Store, accessor metav1.Object, options *metav1.DeleteOptions) bool { func shouldOrphanDependents(ctx genericapirequest.Context, e *Store, accessor metav1.Object, options *metav1.DeleteOptions) bool {
if gcStrategy, ok := e.DeleteStrategy.(rest.GarbageCollectionDeleteStrategy); ok { // Get default GC policy from this REST object type
if gcStrategy.DefaultGarbageCollectionPolicy() == rest.Unsupported { gcStrategy, ok := e.DeleteStrategy.(rest.GarbageCollectionDeleteStrategy)
// return false to indicate that we should NOT orphan var defaultGCPolicy rest.GarbageCollectionPolicy
return false if ok {
} defaultGCPolicy = gcStrategy.DefaultGarbageCollectionPolicy(ctx)
}
if defaultGCPolicy == rest.Unsupported {
// return false to indicate that we should NOT orphan
return false
} }
// An explicit policy was set at deletion time, that overrides everything // An explicit policy was set at deletion time, that overrides everything
@ -733,10 +738,8 @@ func shouldOrphanDependents(e *Store, accessor metav1.Object, options *metav1.De
} }
// Get default orphan policy from this REST object type if it exists // Get default orphan policy from this REST object type if it exists
if gcStrategy, ok := e.DeleteStrategy.(rest.GarbageCollectionDeleteStrategy); ok { if defaultGCPolicy == rest.OrphanDependents {
if gcStrategy.DefaultGarbageCollectionPolicy() == rest.OrphanDependents { return true
return true
}
} }
return false return false
} }
@ -746,9 +749,9 @@ func shouldOrphanDependents(e *Store, accessor metav1.Object, options *metav1.De
// priority, there are three factors affect whether to add/remove the // priority, there are three factors affect whether to add/remove the
// FinalizerDeleteDependents: options, existing finalizers of the object, and // FinalizerDeleteDependents: options, existing finalizers of the object, and
// e.DeleteStrategy.DefaultGarbageCollectionPolicy. // e.DeleteStrategy.DefaultGarbageCollectionPolicy.
func shouldDeleteDependents(e *Store, accessor metav1.Object, options *metav1.DeleteOptions) bool { func shouldDeleteDependents(ctx genericapirequest.Context, e *Store, accessor metav1.Object, options *metav1.DeleteOptions) bool {
// Get default orphan policy from this REST object type // Get default GC policy from this REST object type
if gcStrategy, ok := e.DeleteStrategy.(rest.GarbageCollectionDeleteStrategy); ok && gcStrategy.DefaultGarbageCollectionPolicy() == rest.Unsupported { if gcStrategy, ok := e.DeleteStrategy.(rest.GarbageCollectionDeleteStrategy); ok && gcStrategy.DefaultGarbageCollectionPolicy(ctx) == rest.Unsupported {
// return false to indicate that we should NOT delete in foreground // return false to indicate that we should NOT delete in foreground
return false return false
} }
@ -789,12 +792,12 @@ func shouldDeleteDependents(e *Store, accessor metav1.Object, options *metav1.De
// The finalizers returned are intended to be handled by the garbage collector. // The finalizers returned are intended to be handled by the garbage collector.
// If garbage collection is disabled for the store, this function returns false // If garbage collection is disabled for the store, this function returns false
// to ensure finalizers aren't set which will never be cleared. // to ensure finalizers aren't set which will never be cleared.
func deletionFinalizersForGarbageCollection(e *Store, accessor metav1.Object, options *metav1.DeleteOptions) (bool, []string) { func deletionFinalizersForGarbageCollection(ctx genericapirequest.Context, e *Store, accessor metav1.Object, options *metav1.DeleteOptions) (bool, []string) {
if !e.EnableGarbageCollection { if !e.EnableGarbageCollection {
return false, []string{} return false, []string{}
} }
shouldOrphan := shouldOrphanDependents(e, accessor, options) shouldOrphan := shouldOrphanDependents(ctx, e, accessor, options)
shouldDeleteDependentInForeground := shouldDeleteDependents(e, accessor, options) shouldDeleteDependentInForeground := shouldDeleteDependents(ctx, e, accessor, options)
newFinalizers := []string{} newFinalizers := []string{}
// first remove both finalizers, add them back if needed. // first remove both finalizers, add them back if needed.
@ -880,7 +883,7 @@ func (e *Store) updateForGracefulDeletionAndFinalizers(ctx genericapirequest.Con
if err != nil { if err != nil {
return nil, err return nil, err
} }
needsUpdate, newFinalizers := deletionFinalizersForGarbageCollection(e, existingAccessor, options) needsUpdate, newFinalizers := deletionFinalizersForGarbageCollection(ctx, e, existingAccessor, options)
if needsUpdate { if needsUpdate {
existingAccessor.SetFinalizers(newFinalizers) existingAccessor.SetFinalizers(newFinalizers)
} }
@ -972,7 +975,7 @@ func (e *Store) Delete(ctx genericapirequest.Context, name string, options *meta
// Handle combinations of graceful deletion and finalization by issuing // Handle combinations of graceful deletion and finalization by issuing
// the correct updates. // the correct updates.
shouldUpdateFinalizers, _ := deletionFinalizersForGarbageCollection(e, accessor, options) shouldUpdateFinalizers, _ := deletionFinalizersForGarbageCollection(ctx, e, accessor, options)
// TODO: remove the check, because we support no-op updates now. // TODO: remove the check, because we support no-op updates now.
if graceful || pendingFinalizers || shouldUpdateFinalizers { if graceful || pendingFinalizers || shouldUpdateFinalizers {
err, ignoreNotFound, deleteImmediately, out, lastExisting = e.updateForGracefulDeletionAndFinalizers(ctx, name, key, options, preconditions, obj) err, ignoreNotFound, deleteImmediately, out, lastExisting = e.updateForGracefulDeletionAndFinalizers(ctx, name, key, options, preconditions, obj)

View File

@ -35,6 +35,7 @@ import (
"k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/apimachinery/pkg/selection" "k8s.io/apimachinery/pkg/selection"
"k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apimachinery/pkg/util/validation/field"
@ -82,7 +83,7 @@ type testOrphanDeleteStrategy struct {
*testRESTStrategy *testRESTStrategy
} }
func (t *testOrphanDeleteStrategy) DefaultGarbageCollectionPolicy() rest.GarbageCollectionPolicy { func (t *testOrphanDeleteStrategy) DefaultGarbageCollectionPolicy(ctx genericapirequest.Context) rest.GarbageCollectionPolicy {
return rest.OrphanDependents return rest.OrphanDependents
} }
@ -2005,3 +2006,93 @@ func denyCreateValidation(obj runtime.Object) error {
func denyUpdateValidation(obj, old runtime.Object) error { func denyUpdateValidation(obj, old runtime.Object) error {
return fmt.Errorf("admission denied") return fmt.Errorf("admission denied")
} }
type fakeStrategy struct {
runtime.ObjectTyper
names.NameGenerator
}
func (fakeStrategy) DefaultGarbageCollectionPolicy(ctx genericapirequest.Context) rest.GarbageCollectionPolicy {
appsv1beta1 := schema.GroupVersion{Group: "apps", Version: "v1beta1"}
appsv1beta2 := schema.GroupVersion{Group: "apps", Version: "v1beta2"}
extensionsv1beta1 := schema.GroupVersion{Group: "extensions", Version: "v1beta1"}
if requestInfo, found := genericapirequest.RequestInfoFrom(ctx); found {
groupVersion := schema.GroupVersion{Group: requestInfo.APIGroup, Version: requestInfo.APIVersion}
switch groupVersion {
case appsv1beta1, appsv1beta2, extensionsv1beta1:
// for back compatibility
return rest.OrphanDependents
default:
return rest.DeleteDependents
}
}
return rest.OrphanDependents
}
func TestDeletionFinalizersForGarbageCollection(t *testing.T) {
destroyFunc, registry := NewTestGenericStoreRegistry(t)
defer destroyFunc()
registry.DeleteStrategy = fakeStrategy{}
registry.EnableGarbageCollection = true
tests := []struct {
requestInfo genericapirequest.RequestInfo
desiredFinalizers []string
isNilRequestInfo bool
changed bool
}{
{
genericapirequest.RequestInfo{
APIGroup: "extensions",
APIVersion: "v1beta1",
},
[]string{metav1.FinalizerOrphanDependents},
false,
true,
},
{
genericapirequest.RequestInfo{
APIGroup: "apps",
APIVersion: "v1beta1",
},
[]string{metav1.FinalizerOrphanDependents},
false,
true,
},
{
genericapirequest.RequestInfo{
APIGroup: "apps",
APIVersion: "v1beta2",
},
[]string{metav1.FinalizerOrphanDependents},
false,
true,
},
{
genericapirequest.RequestInfo{
APIGroup: "apps",
APIVersion: "v1",
},
[]string{},
false,
false,
},
}
for _, test := range tests {
context := genericapirequest.NewContext()
if !test.isNilRequestInfo {
context = genericapirequest.WithRequestInfo(context, &test.requestInfo)
}
changed, finalizers := deletionFinalizersForGarbageCollection(context, registry, &example.ReplicaSet{}, &metav1.DeleteOptions{})
if !changed {
if test.changed {
t.Errorf("%s/%s: no new finalizers are added", test.requestInfo.APIGroup, test.requestInfo.APIVersion)
}
} else if !reflect.DeepEqual(finalizers, test.desiredFinalizers) {
t.Errorf("%s/%s: want %#v, got %#v", test.requestInfo.APIGroup, test.requestInfo.APIVersion,
test.desiredFinalizers, finalizers)
}
}
}

View File

@ -48,7 +48,7 @@ const (
// orphan dependents by default. // orphan dependents by default.
type GarbageCollectionDeleteStrategy interface { type GarbageCollectionDeleteStrategy interface {
// DefaultGarbageCollectionPolicy returns the default garbage collection behavior. // DefaultGarbageCollectionPolicy returns the default garbage collection behavior.
DefaultGarbageCollectionPolicy() GarbageCollectionPolicy DefaultGarbageCollectionPolicy(ctx genericapirequest.Context) GarbageCollectionPolicy
} }
// RESTGracefulDeleteStrategy must be implemented by the registry that supports // RESTGracefulDeleteStrategy must be implemented by the registry that supports

View File

@ -17,6 +17,7 @@ go_test(
deps = [ deps = [
"//pkg/api/v1/pod:go_default_library", "//pkg/api/v1/pod:go_default_library",
"//pkg/controller/replicaset:go_default_library", "//pkg/controller/replicaset:go_default_library",
"//pkg/util/slice:go_default_library",
"//test/integration/framework:go_default_library", "//test/integration/framework:go_default_library",
"//test/utils:go_default_library", "//test/utils:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library",

View File

@ -40,6 +40,7 @@ import (
"k8s.io/client-go/util/retry" "k8s.io/client-go/util/retry"
podutil "k8s.io/kubernetes/pkg/api/v1/pod" podutil "k8s.io/kubernetes/pkg/api/v1/pod"
"k8s.io/kubernetes/pkg/controller/replicaset" "k8s.io/kubernetes/pkg/controller/replicaset"
"k8s.io/kubernetes/pkg/util/slice"
"k8s.io/kubernetes/test/integration/framework" "k8s.io/kubernetes/test/integration/framework"
testutil "k8s.io/kubernetes/test/utils" testutil "k8s.io/kubernetes/test/utils"
) )
@ -924,3 +925,115 @@ func TestFullyLabeledReplicas(t *testing.T) {
t.Fatalf("Failed to verify only one pod is fully labeled: %v", err) t.Fatalf("Failed to verify only one pod is fully labeled: %v", err)
} }
} }
func TestReplicaSetsExtensionsV1beta1DefaultGCPolicy(t *testing.T) {
s, closeFn, rm, informers, c := rmSetup(t)
defer closeFn()
ns := framework.CreateTestingNamespace("test-default-gc-extensions", s, t)
defer framework.DeleteTestingNamespace(ns, s, t)
stopCh := runControllerAndInformers(t, rm, informers, 0)
defer close(stopCh)
rs := newRS("rs", ns.Name, 2)
fakeFinalizer := "kube.io/dummy-finalizer"
rs.Finalizers = []string{fakeFinalizer}
rss, _ := createRSsPods(t, c, []*v1beta1.ReplicaSet{rs}, []*v1.Pod{})
rs = rss[0]
waitRSStable(t, c, rs)
// Verify RS creates 2 pods
podClient := c.CoreV1().Pods(ns.Name)
pods := getPods(t, podClient, labelMap())
if len(pods.Items) != 2 {
t.Fatalf("len(pods) = %d, want 2", len(pods.Items))
}
rsClient := c.ExtensionsV1beta1().ReplicaSets(ns.Name)
err := rsClient.Delete(rs.Name, nil)
if err != nil {
t.Fatalf("Failed to delete rs: %v", err)
}
// Verify orphan finalizer has been added
if err := wait.PollImmediate(interval, timeout, func() (bool, error) {
newRS, err := rsClient.Get(rs.Name, metav1.GetOptions{})
if err != nil {
return false, err
}
return slice.ContainsString(newRS.Finalizers, metav1.FinalizerOrphanDependents, nil), nil
}); err != nil {
t.Fatalf("Failed to verify orphan finalizer is added: %v", err)
}
updateRS(t, rsClient, rs.Name, func(rs *v1beta1.ReplicaSet) {
var finalizers []string
// remove fakeFinalizer
for _, finalizer := range rs.Finalizers {
if finalizer != fakeFinalizer {
finalizers = append(finalizers, finalizer)
}
}
rs.Finalizers = finalizers
})
rsClient.Delete(rs.Name, nil)
}
func TestReplicaSetsAppsV1DefaultGCPolicy(t *testing.T) {
s, closeFn, rm, informers, c := rmSetup(t)
defer closeFn()
ns := framework.CreateTestingNamespace("test-default-gc-extensions", s, t)
defer framework.DeleteTestingNamespace(ns, s, t)
stopCh := runControllerAndInformers(t, rm, informers, 0)
defer close(stopCh)
rs := newRS("rs", ns.Name, 2)
fakeFinalizer := "kube.io/dummy-finalizer"
rs.Finalizers = []string{fakeFinalizer}
rss, _ := createRSsPods(t, c, []*v1beta1.ReplicaSet{rs}, []*v1.Pod{})
rs = rss[0]
waitRSStable(t, c, rs)
// Verify RS creates 2 pods
podClient := c.CoreV1().Pods(ns.Name)
pods := getPods(t, podClient, labelMap())
if len(pods.Items) != 2 {
t.Fatalf("len(pods) = %d, want 2", len(pods.Items))
}
rsClient := c.AppsV1().ReplicaSets(ns.Name)
err := rsClient.Delete(rs.Name, nil)
if err != nil {
t.Fatalf("Failed to delete rs: %v", err)
}
// Verify no new finalizer has been added
if err := wait.PollImmediate(interval, timeout, func() (bool, error) {
newRS, err := rsClient.Get(rs.Name, metav1.GetOptions{})
if err != nil {
return false, err
}
if newRS.DeletionTimestamp == nil {
return false, nil
}
if got, want := newRS.Finalizers, []string{fakeFinalizer}; !reflect.DeepEqual(got, want) {
return false, fmt.Errorf("got finalizers: %+v; want: %+v", got, want)
}
return true, nil
}); err != nil {
t.Fatalf("Failed to verify the finalizer: %v", err)
}
updateRS(t, c.ExtensionsV1beta1().ReplicaSets(ns.Name), rs.Name, func(rs *v1beta1.ReplicaSet) {
var finalizers []string
// remove fakeFinalizer
for _, finalizer := range rs.Finalizers {
if finalizer != fakeFinalizer {
finalizers = append(finalizers, finalizer)
}
}
rs.Finalizers = finalizers
})
rsClient.Delete(rs.Name, nil)
}