Skip to content

Commit

Permalink
Allow building a fake client with graceful deleting Pods
Browse files Browse the repository at this point in the history
Signed-off-by: arkbriar <arkbriar@gmail.com>
  • Loading branch information
arkbriar committed May 30, 2023
1 parent 30eae58 commit 601faf2
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 3 deletions.
43 changes: 40 additions & 3 deletions pkg/client/fake/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (

// Using v4 to match upstream
jsonpatch "github.com/evanphx/json-patch"

"sigs.k8s.io/controller-runtime/pkg/client/interceptor"

corev1 "k8s.io/api/core/v1"
Expand All @@ -52,6 +53,8 @@ import (
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/testing"
"k8s.io/utils/pointer"

"sigs.k8s.io/controller-runtime/pkg/internal/field/selector"

"sigs.k8s.io/controller-runtime/pkg/client"
Expand Down Expand Up @@ -290,9 +293,22 @@ func (t versionedTracker) Add(obj runtime.Object) error {
if err != nil {
return fmt.Errorf("failed to get accessor for object: %w", err)
}
if accessor.GetDeletionTimestamp() != nil && len(accessor.GetFinalizers()) == 0 {
return fmt.Errorf("refusing to create obj %s with metadata.deletionTimestamp but no finalizers", accessor.GetName())

gvk, err := apiutil.GVKForObject(obj, t.scheme)
if err != nil {
panic(fmt.Errorf("failed to get gvk for object %T: %w", obj, err))
}

if !allowsGracefulDelete(gvk, obj) {
if accessor.GetDeletionTimestamp() != nil && len(accessor.GetFinalizers()) == 0 {
return fmt.Errorf("refusing to create obj %s with metadata.deletionTimestamp but no finalizers", accessor.GetName())
}
} else {
if accessor.GetDeletionTimestamp() != nil && pointer.Int64Deref(accessor.GetDeletionGracePeriodSeconds(), 0) <= 0 {
return fmt.Errorf("refusing to create obj %s with metadata.deletionTimestamp and non-positive metadata.deletionGracePeriodSeconds", accessor.GetName())
}
}

if accessor.GetResourceVersion() == "" {
// We use a "magic" value of 999 here because this field
// is parsed as uint and and 0 is already used in Update.
Expand Down Expand Up @@ -1075,7 +1091,7 @@ func (c *fakeClient) deleteObject(gvr schema.GroupVersionResource, accessor meta
}
}

//TODO: implement propagation
// TODO: implement propagation
return c.tracker.Delete(gvr, accessor.GetNamespace(), accessor.GetName())
}

Expand Down Expand Up @@ -1281,3 +1297,24 @@ func zero(x interface{}) {
res := reflect.ValueOf(x).Elem()
res.Set(reflect.Zero(res.Type()))
}

func allowsGracefulDelete(gvk schema.GroupVersionKind, obj runtime.Object) bool {
// If the object is a Pod, it can have the deletionTimestamp set without any finalizers.
// This is a special case allowed with graceful deletion. For more details, please see:
// https://github.com/kubernetes/kubernetes/blob/7935006af2925dc081605c17fcb5e656cedee286/pkg/registry/core/pod/strategy.go#L163-L194
if gvk.Group == corev1.GroupName && gvk.Kind == "Pod" && gvk.Version == "v1" {
pod := obj.(*corev1.Pod)
// If the pod is not scheduled, it will be deleted immediately.
if len(pod.Spec.NodeName) == 0 {
return false
}
// If the pod is already terminated, it will be deleted immediately.
if pod.Status.Phase == corev1.PodFailed || pod.Status.Phase == corev1.PodSucceeded {
return false
}
// No need to check the spec.terminationGracePeriodSeconds field because it can be
// overridden by the DeleteOptions.
return true
}
return false
}
39 changes: 39 additions & 0 deletions pkg/client/fake/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/utils/pointer"

"sigs.k8s.io/controller-runtime/pkg/client"
)

Expand Down Expand Up @@ -1017,6 +1019,43 @@ var _ = Describe("Fake client", func() {

})

It("should allow deletionTimestamp without finalizer on Build for Pod", func() {
namespacedName := types.NamespacedName{
Name: "test-pod",
Namespace: "allow-deletiontimestamp-no-finalizers-pod",
}

By("Build with a new Pod without finalizer and deletion grace period seconds")
obj := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: namespacedName.Name,
Namespace: namespacedName.Namespace,
DeletionTimestamp: &metav1.Time{Time: time.Now()},
},
}
Expect(func() { NewClientBuilder().WithObjects(obj).Build() }).To(Panic())

By("Build with a scheduled Pod with deletion grace period seconds and without finalizer")
newObj := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: namespacedName.Name,
Namespace: namespacedName.Namespace,
DeletionTimestamp: &metav1.Time{Time: time.Now()},
DeletionGracePeriodSeconds: pointer.Int64(10),
},
Spec: corev1.PodSpec{
NodeName: "node",
},
}

cl := NewClientBuilder().WithObjects(newObj).Build()

By("Getting the Pod")
obj = &corev1.Pod{}
err := cl.Get(context.Background(), namespacedName, obj)
Expect(err).To(BeNil())
})

It("should reject changes to deletionTimestamp on Patch", func() {
namespacedName := types.NamespacedName{
Name: "test-cm",
Expand Down

0 comments on commit 601faf2

Please sign in to comment.