test: add unit tests for deleteObject NotFound handling in garbage collector

When deleteObject returns a NotFound error (the object was externally deleted
between the GET and the DELETE), attemptToDeleteItem should enqueue a virtual
delete event and return enqueuedVirtualDeleteEventErr.

Cover both code paths:
- default (background propagation): item with dangling owner
- waitingForDependentsDeletion: item whose owner is foreground-deleting
This commit is contained in:
Alessio Attilio
2026-03-10 19:11:31 +01:00
parent 4810a2aae7
commit 8ed40e7ae7
2 changed files with 121 additions and 2 deletions

View File

@@ -627,7 +627,6 @@ func (gc *GarbageCollector) attemptToDeleteItem(ctx context.Context, item *node)
policy := metav1.DeletePropagationForeground
err := gc.deleteObject(item.identity, latest.ResourceVersion, latest.OwnerReferences, &policy)
if errors.IsNotFound(err) {
// the item was deleted externally, enqueue a virtual delete event
gc.dependencyGraphBuilder.enqueueVirtualDeleteEvent(item.identity)
return enqueuedVirtualDeleteEventErr
}
@@ -654,7 +653,6 @@ func (gc *GarbageCollector) attemptToDeleteItem(ctx context.Context, item *node)
)
err := gc.deleteObject(item.identity, latest.ResourceVersion, latest.OwnerReferences, &policy)
if errors.IsNotFound(err) {
// the item was deleted externally, enqueue a virtual delete event
gc.dependencyGraphBuilder.enqueueVirtualDeleteEvent(item.identity)
return enqueuedVirtualDeleteEventErr
}

View File

@@ -18,6 +18,7 @@ package garbagecollector
import (
"context"
goerrors "errors"
"fmt"
"net/http"
"net/http/httptest"
@@ -255,6 +256,126 @@ func serilizeOrDie(t *testing.T, object interface{}) []byte {
return data
}
func TestAttemptToDeleteItemDeleteObjectNotFound(t *testing.T) {
pod := getPod("ExternallyDeletedPod", []metav1.OwnerReference{
{
Kind: "ReplicationController",
Name: "owner1",
UID: "123",
APIVersion: "v1",
},
})
testHandler := &fakeActionHandler{
response: map[string]FakeResponse{
"GET" + "/api/v1/namespaces/ns1/replicationcontrollers/owner1": {
404,
[]byte{},
},
"GET" + "/api/v1/namespaces/ns1/pods/ExternallyDeletedPod": {
200,
serilizeOrDie(t, pod),
},
"DELETE" + "/api/v1/namespaces/ns1/pods/ExternallyDeletedPod": {
404,
[]byte{},
},
},
}
srv, clientConfig := testServerAndClientConfig(testHandler.ServeHTTP)
defer srv.Close()
gc := setupGC(t, clientConfig)
defer close(gc.stop)
item := &node{
identity: objectReference{
OwnerReference: metav1.OwnerReference{
Kind: pod.Kind,
APIVersion: pod.APIVersion,
Name: pod.Name,
UID: pod.UID,
},
Namespace: pod.Namespace,
},
owners: nil,
}
err := gc.attemptToDeleteItem(context.TODO(), item)
if !goerrors.Is(err, enqueuedVirtualDeleteEventErr) {
t.Errorf("expected enqueuedVirtualDeleteEventErr, got: %v", err)
}
if gc.dependencyGraphBuilder.graphChanges.Len() == 0 {
t.Errorf("expected a virtual delete event to be enqueued in graphChanges, but the queue is empty")
}
}
func TestAttemptToDeleteItemDeleteObjectNotFoundWaitingForDependents(t *testing.T) {
pod := getPod("ExternallyDeletedPodFG", []metav1.OwnerReference{
{
Kind: "ReplicationController",
Name: "owner1",
UID: "123",
APIVersion: "v1",
BlockOwnerDeletion: func() *bool { b := true; return &b }(),
},
})
owner := &v1.ReplicationController{
TypeMeta: metav1.TypeMeta{
Kind: "ReplicationController",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "owner1",
Namespace: "ns1",
UID: "123",
DeletionTimestamp: func() *metav1.Time { t := metav1.Now(); return &t }(),
Finalizers: []string{metav1.FinalizerDeleteDependents},
},
}
testHandler := &fakeActionHandler{
response: map[string]FakeResponse{
"GET" + "/api/v1/namespaces/ns1/replicationcontrollers/owner1": {
200,
serilizeOrDie(t, owner),
},
"GET" + "/api/v1/namespaces/ns1/pods/ExternallyDeletedPodFG": {
200,
serilizeOrDie(t, pod),
},
"DELETE" + "/api/v1/namespaces/ns1/pods/ExternallyDeletedPodFG": {
404,
[]byte{},
},
},
}
srv, clientConfig := testServerAndClientConfig(testHandler.ServeHTTP)
defer srv.Close()
gc := setupGC(t, clientConfig)
defer close(gc.stop)
item := &node{
identity: objectReference{
OwnerReference: metav1.OwnerReference{
Kind: pod.Kind,
APIVersion: pod.APIVersion,
Name: pod.Name,
UID: pod.UID,
},
Namespace: pod.Namespace,
},
owners: nil,
}
err := gc.attemptToDeleteItem(context.TODO(), item)
if !goerrors.Is(err, enqueuedVirtualDeleteEventErr) {
t.Errorf("expected enqueuedVirtualDeleteEventErr, got: %v", err)
}
if gc.dependencyGraphBuilder.graphChanges.Len() == 0 {
t.Errorf("expected a virtual delete event to be enqueued in graphChanges, but the queue is empty")
}
}
// test the attemptToDeleteItem function making the expected actions.
func TestAttemptToDeleteItem(t *testing.T) {
pod := getPod("ToBeDeletedPod", []metav1.OwnerReference{