Unverified Commit 00b2d95c authored by Kubernetes Submit Queue's avatar Kubernetes Submit Queue Committed by GitHub

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. ```
parents 47611b3e 344fe56e
...@@ -19,11 +19,14 @@ go_library( ...@@ -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",
......
...@@ -17,8 +17,11 @@ limitations under the License. ...@@ -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 { ...@@ -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
} }
......
...@@ -91,12 +91,59 @@ func TestStatefulSetStrategy(t *testing.T) { ...@@ -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)
}
} }
} }
......
...@@ -39,7 +39,7 @@ var Strategy = cronJobStrategy{legacyscheme.Scheme, names.SimpleNameGenerator} ...@@ -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
} }
......
...@@ -96,7 +96,7 @@ func TestCronJobStrategy(t *testing.T) { ...@@ -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)
} }
} }
......
...@@ -47,7 +47,7 @@ var Strategy = jobStrategy{legacyscheme.Scheme, names.SimpleNameGenerator} ...@@ -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
} }
......
...@@ -105,7 +105,7 @@ func TestJobStrategy(t *testing.T) { ...@@ -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)
} }
} }
......
...@@ -42,7 +42,7 @@ type eventStrategy struct { ...@@ -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
} }
......
...@@ -51,7 +51,7 @@ var Strategy = rcStrategy{legacyscheme.Scheme, names.SimpleNameGenerator} ...@@ -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
} }
......
...@@ -18,6 +18,7 @@ go_library( ...@@ -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",
......
...@@ -17,6 +17,7 @@ limitations under the License. ...@@ -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 { ...@@ -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
} }
......
...@@ -35,12 +35,57 @@ const ( ...@@ -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)
}
} }
} }
......
...@@ -20,6 +20,7 @@ go_library( ...@@ -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( ...@@ -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",
], ],
) )
......
...@@ -18,6 +18,7 @@ package deployment ...@@ -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 { ...@@ -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
} }
......
...@@ -25,6 +25,7 @@ import ( ...@@ -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 ...@@ -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)
}
}
}
...@@ -19,6 +19,7 @@ go_library( ...@@ -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( ...@@ -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",
], ],
) )
......
...@@ -22,6 +22,7 @@ import ( ...@@ -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 { ...@@ -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
} }
......
...@@ -23,6 +23,7 @@ import ( ...@@ -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 ...@@ -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)
}
}
}
...@@ -24,6 +24,7 @@ go_test( ...@@ -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",
......
...@@ -699,12 +699,17 @@ var ( ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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)
......
...@@ -35,6 +35,7 @@ import ( ...@@ -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 { ...@@ -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 { ...@@ -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)
}
}
}
...@@ -48,7 +48,7 @@ const ( ...@@ -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
......
...@@ -17,6 +17,7 @@ go_test( ...@@ -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",
......
...@@ -40,6 +40,7 @@ import ( ...@@ -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) { ...@@ -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)
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment