@@ -18,14 +18,18 @@ package podgc
18
18
19
19
import (
20
20
"context"
21
+ "encoding/json"
21
22
"testing"
22
23
"time"
23
24
24
25
"github.com/google/go-cmp/cmp"
26
+ "github.com/google/go-cmp/cmp/cmpopts"
27
+
25
28
v1 "k8s.io/api/core/v1"
26
29
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27
30
"k8s.io/apimachinery/pkg/labels"
28
31
"k8s.io/apimachinery/pkg/util/sets"
32
+ "k8s.io/apimachinery/pkg/util/strategicpatch"
29
33
"k8s.io/apimachinery/pkg/util/wait"
30
34
utilfeature "k8s.io/apiserver/pkg/util/feature"
31
35
"k8s.io/client-go/informers"
@@ -43,6 +47,7 @@ import (
43
47
"k8s.io/kubernetes/pkg/features"
44
48
"k8s.io/kubernetes/pkg/kubelet/eviction"
45
49
testingclock "k8s.io/utils/clock/testing"
50
+ "k8s.io/utils/pointer"
46
51
)
47
52
48
53
func alwaysReady () bool { return true }
@@ -671,6 +676,128 @@ func TestGCTerminating(t *testing.T) {
671
676
testDeletingPodsMetrics (t , 7 , metrics .PodGCReasonTerminatingOutOfService )
672
677
}
673
678
679
+ func TestGCInspectingPatchedPodBeforeDeletion (t * testing.T ) {
680
+ testCases := []struct {
681
+ name string
682
+ pod * v1.Pod
683
+ expectedPatchedPod * v1.Pod
684
+ expectedDeleteAction * clienttesting.DeleteActionImpl
685
+ }{
686
+ {
687
+ name : "orphaned pod should have DisruptionTarget condition added before deletion" ,
688
+ pod : & v1.Pod {
689
+ ObjectMeta : metav1.ObjectMeta {
690
+ Namespace : "default" ,
691
+ Name : "testPod" ,
692
+ },
693
+ Spec : v1.PodSpec {
694
+ NodeName : "deletedNode" ,
695
+ },
696
+ Status : v1.PodStatus {
697
+ Phase : v1 .PodRunning ,
698
+ Conditions : []v1.PodCondition {
699
+ {
700
+ Type : v1 .PodReady ,
701
+ Status : v1 .ConditionTrue ,
702
+ },
703
+ },
704
+ },
705
+ },
706
+ expectedPatchedPod : & v1.Pod {
707
+ ObjectMeta : metav1.ObjectMeta {
708
+ Namespace : "default" ,
709
+ Name : "testPod" ,
710
+ },
711
+ Spec : v1.PodSpec {
712
+ NodeName : "deletedNode" ,
713
+ },
714
+ Status : v1.PodStatus {
715
+ Phase : v1 .PodFailed ,
716
+ Conditions : []v1.PodCondition {
717
+ {
718
+ Type : v1 .DisruptionTarget ,
719
+ Status : v1 .ConditionTrue ,
720
+ Reason : "DeletionByPodGC" ,
721
+ Message : "PodGC: node no longer exists" ,
722
+ },
723
+ {
724
+ Type : v1 .PodReady ,
725
+ Status : v1 .ConditionTrue ,
726
+ },
727
+ },
728
+ },
729
+ },
730
+ expectedDeleteAction : & clienttesting.DeleteActionImpl {
731
+ Name : "testPod" ,
732
+ DeleteOptions : metav1.DeleteOptions {GracePeriodSeconds : pointer .Int64 (0 )},
733
+ },
734
+ },
735
+ }
736
+
737
+ for _ , test := range testCases {
738
+ t .Run (test .name , func (t * testing.T ) {
739
+ _ , ctx := ktesting .NewTestContext (t )
740
+ defer featuregatetesting .SetFeatureGateDuringTest (t , utilfeature .DefaultFeatureGate , features .PodDisruptionConditions , true )()
741
+
742
+ pods := []* v1.Pod {test .pod }
743
+
744
+ client := setupNewSimpleClient (nil , pods )
745
+ gcc , podInformer , _ := NewFromClient (ctx , client , - 1 )
746
+ gcc .quarantineTime = time .Duration (- 1 )
747
+ podInformer .Informer ().GetStore ().Add (test .pod )
748
+ gcc .gc (ctx )
749
+
750
+ actions := client .Actions ()
751
+
752
+ var patchAction clienttesting.PatchAction
753
+ var deleteAction clienttesting.DeleteAction
754
+
755
+ for _ , action := range actions {
756
+ if action .GetVerb () == "patch" {
757
+ patchAction = action .(clienttesting.PatchAction )
758
+ }
759
+
760
+ if action .GetVerb () == "delete" {
761
+ deleteAction = action .(clienttesting.DeleteAction )
762
+ }
763
+ }
764
+
765
+ if patchAction != nil && test .expectedPatchedPod == nil {
766
+ t .Fatalf ("Pod was pactched but expectedPatchedPod is nil" )
767
+ }
768
+ if test .expectedPatchedPod != nil {
769
+ patchedPodBytes := patchAction .GetPatch ()
770
+ originalPod , err := json .Marshal (test .pod )
771
+ if err != nil {
772
+ t .Fatalf ("Failed to marshal original pod %#v: %v" , originalPod , err )
773
+ }
774
+ updated , err := strategicpatch .StrategicMergePatch (originalPod , patchedPodBytes , v1.Pod {})
775
+ if err != nil {
776
+ t .Fatalf ("Failed to apply strategic merge patch %q on pod %#v: %v" , patchedPodBytes , originalPod , err )
777
+ }
778
+
779
+ updatedPod := & v1.Pod {}
780
+ if err := json .Unmarshal (updated , updatedPod ); err != nil {
781
+ t .Fatalf ("Failed to unmarshal updated pod %q: %v" , updated , err )
782
+ }
783
+
784
+ if diff := cmp .Diff (test .expectedPatchedPod , updatedPod , cmpopts .IgnoreFields (v1.Pod {}, "TypeMeta" ), cmpopts .IgnoreFields (v1.PodCondition {}, "LastTransitionTime" )); diff != "" {
785
+ t .Fatalf ("Unexpected diff on pod (-want,+got):\n %s" , diff )
786
+ }
787
+ }
788
+
789
+ if deleteAction != nil && test .expectedDeleteAction == nil {
790
+ t .Fatalf ("Pod was deleted but expectedDeleteAction is nil" )
791
+ }
792
+ if test .expectedDeleteAction != nil {
793
+ if diff := cmp .Diff (* test .expectedDeleteAction , deleteAction , cmpopts .IgnoreFields (clienttesting.DeleteActionImpl {}, "ActionImpl" )); diff != "" {
794
+ t .Fatalf ("Unexpected diff on deleteAction (-want,+got):\n %s" , diff )
795
+ }
796
+ }
797
+ })
798
+ }
799
+ }
800
+
674
801
func verifyDeletedAndPatchedPods (t * testing.T , client * fake.Clientset , wantDeletedPodNames , wantPatchedPodNames sets.String ) {
675
802
t .Helper ()
676
803
deletedPodNames := getDeletedPodNames (client )
0 commit comments