diff --git a/pkg/controller/garbagecollector/garbagecollector_test.go b/pkg/controller/garbagecollector/garbagecollector_test.go index de037e7409a..398138b0995 100644 --- a/pkg/controller/garbagecollector/garbagecollector_test.go +++ b/pkg/controller/garbagecollector/garbagecollector_test.go @@ -1956,6 +1956,167 @@ func TestConflictingData(t *testing.T) { }), }, }, + { + // https://github.com/kubernetes/kubernetes/issues/98040 + name: "cluster-scoped bad child, namespaced good child, missing parent", + steps: []step{ + // setup + createObjectInClient("", "v1", "pods", "ns1", makeMetadataObj(pod2ns1, pod1ns1)), // good child + createObjectInClient("", "v1", "nodes", "", makeMetadataObj(node1, pod1nonamespace)), // bad child + + // 2,3: observe bad child + processEvent(makeAddEvent(node1, pod1nonamespace)), + assertState(state{ + graphNodes: []*node{ + makeNode(node1, withOwners(pod1nonamespace)), + makeNode(pod1nonamespace, virtual)}, + pendingAttemptToDelete: []*node{ + makeNode(pod1nonamespace, virtual)}, // virtual parent queued for deletion + }), + + // 4,5: observe good child + processEvent(makeAddEvent(pod2ns1, pod1ns1)), + assertState(state{ + graphNodes: []*node{ + makeNode(node1, withOwners(pod1nonamespace)), + makeNode(pod2ns1, withOwners(pod1ns1)), + makeNode(pod1nonamespace, virtual)}, + pendingAttemptToDelete: []*node{ + makeNode(pod1nonamespace, virtual)}, // virtual parent queued for deletion + }), + + // 6,7: process attemptToDelete of bad virtual parent coordinates + processAttemptToDelete(1), + assertState(state{ + graphNodes: []*node{ + makeNode(node1, withOwners(pod1nonamespace)), + makeNode(pod2ns1, withOwners(pod1ns1)), + makeNode(pod1nonamespace, virtual)}, + }), + + // steady-state is bad cluster child and bad virtual parent coordinates, with no retries + assertState(state{ + graphNodes: []*node{ + makeNode(node1, withOwners(pod1nonamespace)), + makeNode(pod1nonamespace, virtual)}, + absentOwnerCache: []objectReference{pod1ns1}, + }), + }, + }, + { + // https://github.com/kubernetes/kubernetes/issues/98040 + name: "namespaced good child, cluster-scoped bad child, missing parent", + steps: []step{ + // setup + createObjectInClient("", "v1", "pods", "ns1", makeMetadataObj(pod2ns1, pod1ns1)), // good child + createObjectInClient("", "v1", "nodes", "", makeMetadataObj(node1, pod1nonamespace)), // bad child + + // 2,3: observe good child + processEvent(makeAddEvent(pod2ns1, pod1ns1)), + assertState(state{ + graphNodes: []*node{ + makeNode(pod2ns1, withOwners(pod1ns1)), + makeNode(pod1ns1, virtual)}, + pendingAttemptToDelete: []*node{ + makeNode(pod1ns1, virtual)}, // virtual parent queued for deletion + }), + + // 4,5: observe bad child + processEvent(makeAddEvent(node1, pod1nonamespace)), + assertState(state{ + graphNodes: []*node{ + makeNode(pod2ns1, withOwners(pod1ns1)), + makeNode(node1, withOwners(pod1nonamespace)), + makeNode(pod1ns1, virtual)}, + pendingAttemptToDelete: []*node{ + makeNode(pod1ns1, virtual), // virtual parent queued for deletion + makeNode(node1, withOwners(pod1nonamespace)), // mismatched child queued for deletion + }, + }), + + // 6,7: process attemptToDelete of good virtual parent coordinates + processAttemptToDelete(1), + assertState(state{ + clientActions: []string{ + "get /v1, Resource=pods ns=ns1 name=podname1", // lookup of missing parent, returns 404 + }, + graphNodes: []*node{ + makeNode(node1, withOwners(pod1nonamespace)), + makeNode(pod2ns1, withOwners(pod1ns1)), + makeNode(pod1ns1, virtual)}, + pendingGraphChanges: []*event{makeVirtualDeleteEvent(pod1ns1)}, // virtual parent not found, queued virtual delete event + pendingAttemptToDelete: []*node{ + makeNode(node1, withOwners(pod1nonamespace)), // mismatched child still queued for deletion + }, + }), + + // 8,9: process attemptToDelete of bad cluster child + processAttemptToDelete(1), + assertState(state{ + clientActions: []string{ + "get /v1, Resource=nodes name=nodename", // lookup of existing node + }, + graphNodes: []*node{ + makeNode(node1, withOwners(pod1nonamespace)), + makeNode(pod2ns1, withOwners(pod1ns1)), + makeNode(pod1ns1, virtual)}, + pendingGraphChanges: []*event{makeVirtualDeleteEvent(pod1ns1)}, // virtual parent virtual delete event still enqueued + }), + + // 10,11: process virtual delete event for good virtual parent coordinates + processPendingGraphChanges(1), + assertState(state{ + graphNodes: []*node{ + makeNode(node1, withOwners(pod1nonamespace)), + makeNode(pod2ns1, withOwners(pod1ns1)), + makeNode(pod1nonamespace, virtual)}, // missing virtual parent replaced with alternate coordinates, still virtual + absentOwnerCache: []objectReference{pod1ns1}, // cached absence of missing parent + pendingAttemptToDelete: []*node{ + makeNode(pod2ns1, withOwners(pod1ns1)), // good child of missing parent enqueued for deletion + makeNode(pod1nonamespace, virtual), // new virtual parent coordinates enqueued for deletion + }, + }), + + // 12,13: process attemptToDelete of good child + processAttemptToDelete(1), + assertState(state{ + clientActions: []string{ + "get /v1, Resource=pods ns=ns1 name=podname2", // lookup of good child + "delete /v1, Resource=pods ns=ns1 name=podname2", // delete of good child + }, + graphNodes: []*node{ + makeNode(node1, withOwners(pod1nonamespace)), + makeNode(pod2ns1, withOwners(pod1ns1)), + makeNode(pod1nonamespace, virtual)}, + absentOwnerCache: []objectReference{pod1ns1}, + pendingAttemptToDelete: []*node{ + makeNode(pod1nonamespace, virtual), // new virtual parent coordinates enqueued for deletion + }, + }), + + // 14,15: observe deletion of good child + processEvent(makeDeleteEvent(pod2ns1, pod1ns1)), + assertState(state{ + graphNodes: []*node{ + makeNode(node1, withOwners(pod1nonamespace)), + makeNode(pod1nonamespace, virtual)}, + absentOwnerCache: []objectReference{pod1ns1}, + pendingAttemptToDelete: []*node{ + makeNode(pod1nonamespace, virtual), // new virtual parent coordinates enqueued for deletion + }, + }), + + // 16,17: process attemptToDelete of bad virtual parent coordinates + // steady-state is bad cluster child and bad virtual parent coordinates, with no retries + processAttemptToDelete(1), + assertState(state{ + graphNodes: []*node{ + makeNode(node1, withOwners(pod1nonamespace)), + makeNode(pod1nonamespace, virtual)}, + absentOwnerCache: []objectReference{pod1ns1}, + }), + }, + }, } alwaysStarted := make(chan struct{})