istio.io/istio@v0.0.0-20240520182934-d79c90f27776/cni/pkg/repair/repair_test.go (about) 1 // Copyright Istio Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package repair 16 17 import ( 18 "fmt" 19 "strings" 20 "testing" 21 "time" 22 23 corev1 "k8s.io/api/core/v1" 24 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 klabels "k8s.io/apimachinery/pkg/labels" 26 "k8s.io/apimachinery/pkg/runtime" 27 28 "istio.io/istio/cni/pkg/config" 29 "istio.io/istio/pkg/kube" 30 "istio.io/istio/pkg/monitoring/monitortest" 31 "istio.io/istio/pkg/slices" 32 "istio.io/istio/pkg/test" 33 "istio.io/istio/pkg/test/util/assert" 34 "istio.io/istio/tools/istio-iptables/pkg/constants" 35 ) 36 37 func TestMatchesFilter(t *testing.T) { 38 makeDetectPod := func(name string, terminationMessage string, exitCode int) *corev1.Pod { 39 return makePod(makePodArgs{ 40 PodName: name, 41 Annotations: map[string]string{"sidecar.istio.io/status": "something"}, 42 InitContainerStatus: &corev1.ContainerStatus{ 43 Name: constants.ValidationContainerName, 44 State: corev1.ContainerState{ 45 Waiting: &corev1.ContainerStateWaiting{ 46 Reason: "CrashLoopBackOff", 47 Message: "Back-off 5m0s restarting failed blah blah blah", 48 }, 49 }, 50 LastTerminationState: corev1.ContainerState{ 51 Terminated: &corev1.ContainerStateTerminated{ 52 Message: terminationMessage, 53 ExitCode: int32(exitCode), 54 }, 55 }, 56 }, 57 }) 58 } 59 60 cases := []struct { 61 name string 62 config config.RepairConfig 63 pod *corev1.Pod 64 want bool 65 }{ 66 { 67 "Testing OK pod with only ExitCode check", 68 config.RepairConfig{ 69 SidecarAnnotation: "sidecar.istio.io/status", 70 InitContainerName: constants.ValidationContainerName, 71 InitExitCode: 126, 72 }, 73 workingPod, 74 false, 75 }, 76 { 77 "Testing working pod (that previously died) with only ExitCode check", 78 config.RepairConfig{ 79 SidecarAnnotation: "sidecar.istio.io/status", 80 InitContainerName: constants.ValidationContainerName, 81 InitExitCode: 126, 82 }, 83 workingPodDiedPreviously, 84 false, 85 }, 86 { 87 "Testing broken pod (in waiting state) with only ExitCode check", 88 config.RepairConfig{ 89 SidecarAnnotation: "sidecar.istio.io/status", 90 InitContainerName: constants.ValidationContainerName, 91 InitExitCode: 126, 92 }, 93 brokenPodWaiting, 94 true, 95 }, 96 { 97 "Testing broken pod (in terminating state) with only ExitCode check", 98 config.RepairConfig{ 99 SidecarAnnotation: "sidecar.istio.io/status", 100 InitContainerName: constants.ValidationContainerName, 101 InitExitCode: 126, 102 }, 103 brokenPodTerminating, 104 true, 105 }, 106 { 107 "Testing broken pod with wrong ExitCode", 108 config.RepairConfig{ 109 SidecarAnnotation: "sidecar.istio.io/status", 110 InitContainerName: constants.ValidationContainerName, 111 InitExitCode: 55, 112 }, 113 brokenPodWaiting, 114 false, 115 }, 116 { 117 "Testing broken pod with no annotation (should be ignored)", 118 config.RepairConfig{ 119 SidecarAnnotation: "sidecar.istio.io/status", 120 InitContainerName: constants.ValidationContainerName, 121 InitExitCode: 126, 122 }, 123 brokenPodNoAnnotation, 124 false, 125 }, 126 { 127 "Check termination message match false", 128 config.RepairConfig{ 129 SidecarAnnotation: "sidecar.istio.io/status", 130 InitContainerName: constants.ValidationContainerName, 131 InitTerminationMsg: "Termination Message", 132 }, 133 makeDetectPod( 134 "TerminationMessageMatchFalse", 135 "This Does Not Match", 136 0), 137 false, 138 }, 139 { 140 "Check termination message match true", 141 config.RepairConfig{ 142 SidecarAnnotation: "sidecar.istio.io/status", 143 InitContainerName: constants.ValidationContainerName, 144 InitTerminationMsg: "Termination Message", 145 }, 146 makeDetectPod( 147 "TerminationMessageMatchTrue", 148 "Termination Message", 149 0), 150 true, 151 }, 152 { 153 "Check termination message match true for trailing and leading space", 154 config.RepairConfig{ 155 SidecarAnnotation: "sidecar.istio.io/status", 156 InitContainerName: constants.ValidationContainerName, 157 InitTerminationMsg: " Termination Message", 158 }, 159 makeDetectPod( 160 "TerminationMessageMatchTrueLeadingSpace", 161 "Termination Message ", 162 0), 163 true, 164 }, 165 { 166 "Check termination code match false", 167 config.RepairConfig{ 168 SidecarAnnotation: "sidecar.istio.io/status", 169 InitContainerName: constants.ValidationContainerName, 170 InitExitCode: 126, 171 }, 172 makeDetectPod( 173 "TerminationCodeMatchFalse", 174 "", 175 121), 176 false, 177 }, 178 { 179 "Check termination code match true", 180 config.RepairConfig{ 181 SidecarAnnotation: "sidecar.istio.io/status", 182 InitContainerName: constants.ValidationContainerName, 183 InitExitCode: 126, 184 }, 185 makeDetectPod( 186 "TerminationCodeMatchTrue", 187 "", 188 126), 189 true, 190 }, 191 { 192 "Check badly formatted pod", 193 config.RepairConfig{ 194 SidecarAnnotation: "sidecar.istio.io/status", 195 InitContainerName: constants.ValidationContainerName, 196 InitExitCode: 126, 197 }, 198 makePod(makePodArgs{ 199 PodName: "Test", 200 Annotations: map[string]string{"sidecar.istio.io/status": "something"}, 201 InitContainerStatus: &corev1.ContainerStatus{}, 202 }), 203 false, 204 }, 205 } 206 for _, tt := range cases { 207 t.Run(tt.name, func(t *testing.T) { 208 c := &Controller{cfg: tt.config} 209 assert.Equal(t, c.matchesFilter(tt.pod), tt.want) 210 }) 211 } 212 } 213 214 func fakeClient(pods ...*corev1.Pod) kube.Client { 215 var csPods []runtime.Object 216 217 for _, pod := range pods { 218 csPods = append(csPods, pod.DeepCopy()) 219 } 220 return kube.NewFakeClient(csPods...) 221 } 222 223 func makePodLabelMap(pods []*corev1.Pod) (podmap map[string]string) { 224 podmap = map[string]string{} 225 for _, pod := range pods { 226 podmap[pod.Name] = "" 227 for key, value := range pod.Labels { 228 podmap[pod.Name] = strings.Join([]string{podmap[pod.Name], fmt.Sprintf("%s=%s", key, value)}, ",") 229 } 230 podmap[pod.Name] = strings.Trim(podmap[pod.Name], " ,") 231 } 232 return 233 } 234 235 func TestLabelPods(t *testing.T) { 236 tests := []struct { 237 name string 238 client kube.Client 239 config config.RepairConfig 240 wantLabels map[string]string 241 wantCount float64 242 wantTags map[string]string 243 }{ 244 { 245 name: "No broken pods", 246 client: fakeClient(workingPod, workingPodDiedPreviously), 247 config: config.RepairConfig{ 248 InitContainerName: constants.ValidationContainerName, 249 InitExitCode: 126, 250 InitTerminationMsg: "Died for some reason", 251 LabelKey: "testkey", 252 LabelValue: "testval", 253 }, 254 wantLabels: map[string]string{workingPod.Name: "", workingPodDiedPreviously.Name: ""}, 255 wantCount: 0, 256 }, 257 { 258 name: "With broken pods", 259 client: fakeClient(workingPod, workingPodDiedPreviously, brokenPodWaiting), 260 config: config.RepairConfig{ 261 InitContainerName: constants.ValidationContainerName, 262 InitExitCode: 126, 263 InitTerminationMsg: "Died for some reason", 264 LabelKey: "testkey", 265 LabelValue: "testval", 266 }, 267 wantLabels: map[string]string{workingPod.Name: "", workingPodDiedPreviously.Name: "", brokenPodWaiting.Name: "testkey=testval"}, 268 wantCount: 1, 269 wantTags: map[string]string{"result": resultSuccess, "type": labelType}, 270 }, 271 { 272 name: "With already labeled pod", 273 client: fakeClient(workingPod, workingPodDiedPreviously, brokenPodTerminating), 274 config: config.RepairConfig{ 275 InitContainerName: constants.ValidationContainerName, 276 InitExitCode: 126, 277 InitTerminationMsg: "Died for some reason", 278 LabelKey: "testlabel", 279 LabelValue: "true", 280 }, 281 wantLabels: map[string]string{workingPod.Name: "", workingPodDiedPreviously.Name: "", brokenPodTerminating.Name: "testlabel=true"}, 282 wantCount: 1, 283 wantTags: map[string]string{"result": resultSkip, "type": labelType}, 284 }, 285 } 286 for _, tt := range tests { 287 t.Run(tt.name, func(t *testing.T) { 288 mt := monitortest.New(t) 289 tt.config.LabelPods = true 290 c, err := NewRepairController(tt.client, tt.config) 291 assert.NoError(t, err) 292 t.Cleanup(func() { 293 assert.NoError(t, c.queue.WaitForClose(time.Second)) 294 }) 295 stop := test.NewStop(t) 296 tt.client.RunAndWait(stop) 297 go c.Run(stop) 298 kube.WaitForCacheSync("test", stop, c.queue.HasSynced) 299 300 assert.EventuallyEqual(t, func() map[string]string { 301 havePods := c.pods.List(metav1.NamespaceAll, klabels.Everything()) 302 slices.SortBy(havePods, func(a *corev1.Pod) string { 303 return a.Name 304 }) 305 return makePodLabelMap(havePods) 306 }, tt.wantLabels) 307 if tt.wantCount > 0 { 308 mt.Assert(podsRepaired.Name(), tt.wantTags, monitortest.Exactly(tt.wantCount)) 309 } 310 }) 311 } 312 } 313 314 func TestDeletePods(t *testing.T) { 315 tests := []struct { 316 name string 317 client kube.Client 318 config config.RepairConfig 319 wantErr bool 320 wantPods []*corev1.Pod 321 wantCount float64 322 wantTags map[string]string 323 }{ 324 { 325 name: "No broken pods", 326 client: fakeClient(workingPod, workingPodDiedPreviously), 327 config: config.RepairConfig{ 328 InitContainerName: constants.ValidationContainerName, 329 InitExitCode: 126, 330 InitTerminationMsg: "Died for some reason", 331 }, 332 wantPods: []*corev1.Pod{workingPod, workingPodDiedPreviously}, 333 wantErr: false, 334 wantCount: 0, 335 }, 336 { 337 name: "With broken pods", 338 client: fakeClient(workingPod, workingPodDiedPreviously, brokenPodWaiting), 339 config: config.RepairConfig{ 340 InitContainerName: constants.ValidationContainerName, 341 InitExitCode: 126, 342 InitTerminationMsg: "Died for some reason", 343 }, 344 wantPods: []*corev1.Pod{workingPod, workingPodDiedPreviously}, 345 wantErr: false, 346 wantCount: 1, 347 wantTags: map[string]string{"result": resultSuccess, "type": deleteType}, 348 }, 349 } 350 for _, tt := range tests { 351 t.Run(tt.name, func(t *testing.T) { 352 mt := monitortest.New(t) 353 tt.config.DeletePods = true 354 c, err := NewRepairController(tt.client, tt.config) 355 assert.NoError(t, err) 356 t.Cleanup(func() { 357 assert.NoError(t, c.queue.WaitForClose(time.Second)) 358 }) 359 stop := test.NewStop(t) 360 tt.client.RunAndWait(stop) 361 go c.Run(stop) 362 kube.WaitForCacheSync("test", stop, c.queue.HasSynced) 363 364 assert.EventuallyEqual(t, func() []*corev1.Pod { 365 havePods := c.pods.List(metav1.NamespaceAll, klabels.Everything()) 366 slices.SortBy(havePods, func(a *corev1.Pod) string { 367 return a.Name 368 }) 369 return havePods 370 }, tt.wantPods) 371 if tt.wantCount > 0 { 372 mt.Assert(podsRepaired.Name(), tt.wantTags, monitortest.Exactly(tt.wantCount)) 373 } 374 }) 375 } 376 }