@@ -939,3 +939,346 @@ func Test_getNodeByIDs(t *testing.T) {
939939 })
940940 }
941941}
942+
943+ func TestPartitionPodsForEviction (t * testing.T ) {
944+ t .Parallel ()
945+
946+ const testCastNamespace = "cast-namespace"
947+
948+ now := time .Now ()
949+ recentDeletion := metav1 .NewTime (now .Add (- 30 * time .Second ))
950+ oldDeletion := metav1 .NewTime (now .Add (- 10 * time .Minute ))
951+
952+ tests := []struct {
953+ name string
954+ pods []v1.Pod
955+ castNamespace string
956+ skipDeletedTimeoutSecs int
957+ wantEvictableLen int
958+ wantNonEvictableLen int
959+ wantCastPodsLen int
960+ wantEvictablePodNames []string
961+ wantNonEvictablePodNames []string
962+ wantCastPodNames []string
963+ }{
964+ {
965+ name : "empty pods returns empty result" ,
966+ pods : []v1.Pod {},
967+ castNamespace : testCastNamespace ,
968+ wantEvictableLen : 0 ,
969+ },
970+ {
971+ name : "regular pods are evictable" ,
972+ pods : []v1.Pod {
973+ {
974+ ObjectMeta : metav1.ObjectMeta {Name : "pod1" , Namespace : "default" },
975+ Status : v1.PodStatus {Phase : v1 .PodRunning },
976+ },
977+ {
978+ ObjectMeta : metav1.ObjectMeta {Name : "pod2" , Namespace : "kube-system" },
979+ Status : v1.PodStatus {Phase : v1 .PodRunning },
980+ },
981+ },
982+ castNamespace : testCastNamespace ,
983+ wantEvictableLen : 2 ,
984+ wantEvictablePodNames : []string {"pod1" , "pod2" },
985+ },
986+ {
987+ name : "daemonset pods are non-evictable" ,
988+ pods : []v1.Pod {
989+ {
990+ ObjectMeta : metav1.ObjectMeta {
991+ Name : "daemonset-pod" ,
992+ Namespace : "default" ,
993+ OwnerReferences : []metav1.OwnerReference {
994+ {Kind : "DaemonSet" , Controller : boolPtr (true )},
995+ },
996+ },
997+ Status : v1.PodStatus {Phase : v1 .PodRunning },
998+ },
999+ },
1000+ castNamespace : testCastNamespace ,
1001+ wantNonEvictableLen : 1 ,
1002+ wantNonEvictablePodNames : []string {"daemonset-pod" },
1003+ },
1004+ {
1005+ name : "static pods are non-evictable" ,
1006+ pods : []v1.Pod {
1007+ {
1008+ ObjectMeta : metav1.ObjectMeta {
1009+ Name : "static-pod" ,
1010+ Namespace : "default" ,
1011+ OwnerReferences : []metav1.OwnerReference {
1012+ {Kind : "Node" , Controller : boolPtr (true )},
1013+ },
1014+ },
1015+ Status : v1.PodStatus {Phase : v1 .PodRunning },
1016+ },
1017+ },
1018+ castNamespace : testCastNamespace ,
1019+ wantNonEvictableLen : 1 ,
1020+ wantNonEvictablePodNames : []string {"static-pod" },
1021+ },
1022+ {
1023+ name : "succeeded pods are skipped" ,
1024+ pods : []v1.Pod {
1025+ {
1026+ ObjectMeta : metav1.ObjectMeta {Name : "completed-pod" , Namespace : "default" },
1027+ Status : v1.PodStatus {Phase : v1 .PodSucceeded },
1028+ },
1029+ },
1030+ castNamespace : testCastNamespace ,
1031+ wantEvictableLen : 0 ,
1032+ },
1033+ {
1034+ name : "failed pods are skipped" ,
1035+ pods : []v1.Pod {
1036+ {
1037+ ObjectMeta : metav1.ObjectMeta {Name : "failed-pod" , Namespace : "default" },
1038+ Status : v1.PodStatus {Phase : v1 .PodFailed },
1039+ },
1040+ },
1041+ castNamespace : testCastNamespace ,
1042+ wantEvictableLen : 0 ,
1043+ },
1044+ {
1045+ name : "cast namespace pods are identified separately" ,
1046+ pods : []v1.Pod {
1047+ {
1048+ ObjectMeta : metav1.ObjectMeta {Name : "cast-pod" , Namespace : testCastNamespace },
1049+ Status : v1.PodStatus {Phase : v1 .PodRunning },
1050+ },
1051+ },
1052+ castNamespace : testCastNamespace ,
1053+ wantEvictableLen : 1 ,
1054+ wantCastPodsLen : 1 ,
1055+ wantCastPodNames : []string {"cast-pod" },
1056+ },
1057+ {
1058+ name : "recently deleted pods are included" ,
1059+ pods : []v1.Pod {
1060+ {
1061+ ObjectMeta : metav1.ObjectMeta {
1062+ Name : "deleted-pod" ,
1063+ Namespace : "default" ,
1064+ DeletionTimestamp : & recentDeletion ,
1065+ },
1066+ Status : v1.PodStatus {Phase : v1 .PodRunning },
1067+ },
1068+ },
1069+ castNamespace : testCastNamespace ,
1070+ skipDeletedTimeoutSecs : 60 ,
1071+ wantEvictableLen : 1 ,
1072+ wantEvictablePodNames : []string {"deleted-pod" },
1073+ },
1074+ {
1075+ name : "old deleted pods are skipped" ,
1076+ pods : []v1.Pod {
1077+ {
1078+ ObjectMeta : metav1.ObjectMeta {
1079+ Name : "old-deleted-pod" ,
1080+ Namespace : "default" ,
1081+ DeletionTimestamp : & oldDeletion ,
1082+ },
1083+ Status : v1.PodStatus {Phase : v1 .PodRunning },
1084+ },
1085+ },
1086+ castNamespace : testCastNamespace ,
1087+ skipDeletedTimeoutSecs : 60 ,
1088+ wantEvictableLen : 0 ,
1089+ },
1090+ {
1091+ name : "mixed pods are partitioned correctly" ,
1092+ pods : []v1.Pod {
1093+ {
1094+ ObjectMeta : metav1.ObjectMeta {Name : "regular-pod" , Namespace : "default" },
1095+ Status : v1.PodStatus {Phase : v1 .PodRunning },
1096+ },
1097+ {
1098+ ObjectMeta : metav1.ObjectMeta {
1099+ Name : "daemonset-pod" ,
1100+ Namespace : "default" ,
1101+ OwnerReferences : []metav1.OwnerReference {
1102+ {Kind : "DaemonSet" , Controller : boolPtr (true )},
1103+ },
1104+ },
1105+ Status : v1.PodStatus {Phase : v1 .PodRunning },
1106+ },
1107+ {
1108+ ObjectMeta : metav1.ObjectMeta {Name : "cast-pod" , Namespace : testCastNamespace },
1109+ Status : v1.PodStatus {Phase : v1 .PodRunning },
1110+ },
1111+ {
1112+ ObjectMeta : metav1.ObjectMeta {Name : "completed-pod" , Namespace : "default" },
1113+ Status : v1.PodStatus {Phase : v1 .PodSucceeded },
1114+ },
1115+ },
1116+ castNamespace : testCastNamespace ,
1117+ wantEvictableLen : 2 ,
1118+ wantNonEvictableLen : 1 ,
1119+ wantCastPodsLen : 1 ,
1120+ wantEvictablePodNames : []string {"regular-pod" , "cast-pod" },
1121+ wantNonEvictablePodNames : []string {"daemonset-pod" },
1122+ wantCastPodNames : []string {"cast-pod" },
1123+ },
1124+ }
1125+
1126+ for _ , tt := range tests {
1127+ t .Run (tt .name , func (t * testing.T ) {
1128+ t .Parallel ()
1129+
1130+ // Convert []v1.Pod to []*v1.Pod
1131+ podPtrs := make ([]* v1.Pod , len (tt .pods ))
1132+ for i := range tt .pods {
1133+ podPtrs [i ] = & tt .pods [i ]
1134+ }
1135+
1136+ result := PartitionPodsForEviction (podPtrs , tt .castNamespace , tt .skipDeletedTimeoutSecs )
1137+
1138+ require .Len (t , result .Evictable , tt .wantEvictableLen )
1139+ require .Len (t , result .NonEvictable , tt .wantNonEvictableLen )
1140+ require .Len (t , result .CastPods , tt .wantCastPodsLen )
1141+
1142+ if len (tt .wantEvictablePodNames ) > 0 {
1143+ evictableNames := make ([]string , len (result .Evictable ))
1144+ for i , p := range result .Evictable {
1145+ evictableNames [i ] = p .Name
1146+ }
1147+ require .ElementsMatch (t , tt .wantEvictablePodNames , evictableNames )
1148+ }
1149+
1150+ if len (tt .wantNonEvictablePodNames ) > 0 {
1151+ nonEvictableNames := make ([]string , len (result .NonEvictable ))
1152+ for i , p := range result .NonEvictable {
1153+ nonEvictableNames [i ] = p .Name
1154+ }
1155+ require .ElementsMatch (t , tt .wantNonEvictablePodNames , nonEvictableNames )
1156+ }
1157+
1158+ if len (tt .wantCastPodNames ) > 0 {
1159+ castPodNames := make ([]string , len (result .CastPods ))
1160+ for i , p := range result .CastPods {
1161+ castPodNames [i ] = p .Name
1162+ }
1163+ require .ElementsMatch (t , tt .wantCastPodNames , castPodNames )
1164+ }
1165+ })
1166+ }
1167+ }
1168+
1169+ func TestIsDaemonSetPod (t * testing.T ) {
1170+ t .Parallel ()
1171+
1172+ tests := []struct {
1173+ name string
1174+ pod * v1.Pod
1175+ want bool
1176+ }{
1177+ {
1178+ name : "returns true for daemonset pod" ,
1179+ pod : & v1.Pod {
1180+ ObjectMeta : metav1.ObjectMeta {
1181+ OwnerReferences : []metav1.OwnerReference {
1182+ {Kind : "DaemonSet" , Controller : boolPtr (true )},
1183+ },
1184+ },
1185+ },
1186+ want : true ,
1187+ },
1188+ {
1189+ name : "returns false for deployment pod" ,
1190+ pod : & v1.Pod {
1191+ ObjectMeta : metav1.ObjectMeta {
1192+ OwnerReferences : []metav1.OwnerReference {
1193+ {Kind : "ReplicaSet" , Controller : boolPtr (true )},
1194+ },
1195+ },
1196+ },
1197+ want : false ,
1198+ },
1199+ {
1200+ name : "returns false for pod without owner" ,
1201+ pod : & v1.Pod {
1202+ ObjectMeta : metav1.ObjectMeta {
1203+ OwnerReferences : nil ,
1204+ },
1205+ },
1206+ want : false ,
1207+ },
1208+ {
1209+ name : "returns false for non-controller daemonset owner" ,
1210+ pod : & v1.Pod {
1211+ ObjectMeta : metav1.ObjectMeta {
1212+ OwnerReferences : []metav1.OwnerReference {
1213+ {Kind : "DaemonSet" , Controller : boolPtr (false )},
1214+ },
1215+ },
1216+ },
1217+ want : false ,
1218+ },
1219+ }
1220+
1221+ for _ , tt := range tests {
1222+ t .Run (tt .name , func (t * testing.T ) {
1223+ t .Parallel ()
1224+
1225+ got := IsDaemonSetPod (tt .pod )
1226+ require .Equal (t , tt .want , got )
1227+ })
1228+ }
1229+ }
1230+
1231+ func TestIsStaticPod (t * testing.T ) {
1232+ t .Parallel ()
1233+
1234+ tests := []struct {
1235+ name string
1236+ pod * v1.Pod
1237+ want bool
1238+ }{
1239+ {
1240+ name : "returns true for static pod" ,
1241+ pod : & v1.Pod {
1242+ ObjectMeta : metav1.ObjectMeta {
1243+ OwnerReferences : []metav1.OwnerReference {
1244+ {Kind : "Node" , Controller : boolPtr (true )},
1245+ },
1246+ },
1247+ },
1248+ want : true ,
1249+ },
1250+ {
1251+ name : "returns false for deployment pod" ,
1252+ pod : & v1.Pod {
1253+ ObjectMeta : metav1.ObjectMeta {
1254+ OwnerReferences : []metav1.OwnerReference {
1255+ {Kind : "ReplicaSet" , Controller : boolPtr (true )},
1256+ },
1257+ },
1258+ },
1259+ want : false ,
1260+ },
1261+ {
1262+ name : "returns false for pod without owner" ,
1263+ pod : & v1.Pod {
1264+ ObjectMeta : metav1.ObjectMeta {
1265+ OwnerReferences : nil ,
1266+ },
1267+ },
1268+ want : false ,
1269+ },
1270+ }
1271+
1272+ for _ , tt := range tests {
1273+ t .Run (tt .name , func (t * testing.T ) {
1274+ t .Parallel ()
1275+
1276+ got := IsStaticPod (tt .pod )
1277+ require .Equal (t , tt .want , got )
1278+ })
1279+ }
1280+ }
1281+
1282+ func boolPtr (b bool ) * bool {
1283+ return & b
1284+ }
0 commit comments