Skip to content

Commit 149d138

Browse files
committed
node drainer added
1 parent bade8dc commit 149d138

File tree

3 files changed

+1795
-0
lines changed

3 files changed

+1795
-0
lines changed

internal/k8s/kubernetes_test.go

Lines changed: 343 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)