Change:
We should enhance the to verify that adding is unit-testable. We originally thought that this would only be possible with finalizers but there looks to be other options. We could follow examples from In podgc, if we discover an orphaned pod (pod on a missing node), we add a DisruptionCondition to the pod. We only test this behavior in the integration tests because we previously rely on finalizers to halt the deletion so we can avoid flaky tests.
This issue is currently awaiting triage. If a SIG or subproject determines this is a relevant issue, they will accept it by applying the label and provide further guidance. The label can be by org members by writing in a comment. <detailsInstructions for interacting with me using PR comments are available . If you have questions or suggestions related to my behavior, please file an issue against the repository. </details
/kind cleanup /sig apps
/good-first-issue /help
Will take a look /assign
/assign
/help
newbie question please, I couldn't find the file in the codebase or the PRs. Where can I read it?
Working on it now!
/assign
/assign
Has done very good work with this so I’m not sure we will be able to accept other PRs. We are waiting for an approval on the PR
is this issue still open
Is the issue still open or awaiting for the approval on the pr?
/remove help /remove good-first-issue
The issue is waiting for approval. I removed the tags to reflect that is is almost done.

Discussion:
import ( "context" <ins> "encoding/json" </ins> "testing" "time" "github.com/google/go-cmp/cmp" <ins> "github.com/google/go-cmp/cmp/cmpopts" </ins> v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/util/sets" <ins> "k8s.io/apimachinery/pkg/util/strategicpatch" </ins> "k8s.io/apimachinery/pkg/util/wait" utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/client-go/informers"

"k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/kubelet/eviction" testingclock "k8s.io/utils/clock/testing" <ins> "k8s.io/utils/pointer" </ins> ) func alwaysReady() bool { return true }

testDeletingPodsMetrics(t, 7, metrics.PodGCReasonTerminatingOutOfService) } <ins> func TestGCInspectingPatchedPodBeforeDeletion(t *testing.T) { testCases := []struct { name                 string pod                  *v1.Pod expectedPatchedPod   *v1.Pod expectedDeleteAction *clienttesting.DeleteActionImpl }{ { name: "orphaned pod should have DisruptionTarget condition added before deletion", pod: &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Namespace: "default", Name:      "testPod", }, Spec: v1.PodSpec{ NodeName: "deletedNode", }, Status: v1.PodStatus{ Phase: v1.PodRunning, Conditions: []v1.PodCondition{ { Type:   v1.PodReady, Status: v1.ConditionTrue, }, }, }, }, expectedPatchedPod: &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Namespace: "default", Name:      "testPod", }, Spec: v1.PodSpec{ NodeName: "deletedNode", }, Status: v1.PodStatus{ Phase: v1.PodFailed, Conditions: []v1.PodCondition{ { Type:    v1.DisruptionTarget, Status:  v1.ConditionTrue, Reason:  "DeletionByPodGC", Message: "PodGC: node no longer exists", }, { Type:   v1.PodReady, Status: v1.ConditionTrue, }, }, }, }, expectedDeleteAction: &clienttesting.DeleteActionImpl{ Name:          "testPod", DeleteOptions: metav1.DeleteOptions{GracePeriodSeconds: pointer.Int64(0)}, }, }, } for _, test := range testCases { t.Run(test.name, func(t *testing.T) { _, ctx := ktesting.NewTestContext(t) defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodDisruptionConditions, true)() pods := []*v1.Pod{test.pod} client := setupNewSimpleClient(nil, pods) gcc, podInformer, _ := NewFromClient(ctx, client, -1) gcc.quarantineTime = time.Duration(-1) podInformer.Informer().GetStore().Add(test.pod) gcc.gc(ctx) actions := client.Actions() var patchAction clienttesting.PatchAction var deleteAction clienttesting.DeleteAction for _, action := range actions { if action.GetVerb() == "patch" { patchAction = action.(clienttesting.PatchAction) } if action.GetVerb() == "delete" { deleteAction = action.(clienttesting.DeleteAction) } } if patchAction != nil && test.expectedPatchedPod == nil { t.Fatalf("Pod was pactched but expectedPatchedPod is nil") } if test.expectedPatchedPod != nil { patchedPodBytes := patchAction.GetPatch() originalPod, err := json.Marshal(test.pod) if err != nil { t.Fatalf("Failed to marshal original pod %#v: %v", originalPod, err) } updated, err := strategicpatch.StrategicMergePatch(originalPod, patchedPodBytes, v1.Pod{}) if err != nil { t.Fatalf("Failed to apply strategic merge patch %q on pod %#v: %v", patchedPodBytes, originalPod, err) } updatedPod := &v1.Pod{} if err := json.Unmarshal(updated, updatedPod); err != nil { t.Fatalf("Failed to unmarshal updated pod %q: %v", updated, err) } if diff := cmp.Diff(test.expectedPatchedPod, updatedPod, cmpopts.IgnoreFields(v1.Pod{}, "TypeMeta"), cmpopts.IgnoreFields(v1.PodCondition{}, "LastTransitionTime")); diff != "" { t.Fatalf("Unexpected diff on pod (-want,+got):n%s", diff) } } if deleteAction != nil && test.expectedDeleteAction == nil { t.Fatalf("Pod was deleted but expectedDeleteAction is nil") } if test.expectedDeleteAction != nil { if diff := cmp.Diff(*test.expectedDeleteAction, deleteAction, cmpopts.IgnoreFields(clienttesting.DeleteActionImpl{}, "ActionImpl")); diff != "" { t.Fatalf("Unexpected diff on deleteAction (-want,+got):n%s", diff) } } }) } } </ins> func verifyDeletedAndPatchedPods(t *testing.T, client *fake.Clientset, wantDeletedPodNames, wantPatchedPodNames sets.String) { t.Helper() deletedPodNames := getDeletedPodNames(client)