istio.io/istio@v0.0.0-20240520182934-d79c90f27776/cni/pkg/nodeagent/informers_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 nodeagent 16 17 import ( 18 "context" 19 "fmt" 20 "testing" 21 "time" 22 23 "github.com/stretchr/testify/mock" 24 corev1 "k8s.io/api/core/v1" 25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 "k8s.io/apimachinery/pkg/types" 27 28 "istio.io/istio/cni/pkg/util" 29 "istio.io/istio/pkg/config/constants" 30 "istio.io/istio/pkg/kube" 31 "istio.io/istio/pkg/monitoring/monitortest" 32 "istio.io/istio/pkg/test/util/assert" 33 ) 34 35 func TestExistingPodAddedWhenNsLabeled(t *testing.T) { 36 setupLogging() 37 NodeName = "testnode" 38 ctx, cancel := context.WithCancel(context.Background()) 39 defer cancel() 40 41 pod := &corev1.Pod{ 42 ObjectMeta: metav1.ObjectMeta{ 43 Name: "test", 44 Namespace: "test", 45 }, 46 Spec: corev1.PodSpec{ 47 NodeName: NodeName, 48 }, 49 Status: corev1.PodStatus{ 50 PodIP: "11.1.1.12", 51 }, 52 } 53 ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test"}} 54 55 client := kube.NewFakeClient(ns, pod) 56 57 // We are expecting at most 1 calls to the mock, wait for them 58 wg, waitForMockCalls := NewWaitForNCalls(t, 1) 59 fs := &fakeServer{testWG: wg} 60 61 fs.On("AddPodToMesh", 62 ctx, 63 pod, 64 util.GetPodIPsIfPresent(pod), 65 "", 66 ).Return(nil) 67 68 server := &meshDataplane{ 69 kubeClient: client.Kube(), 70 netServer: fs, 71 } 72 73 handlers := setupHandlers(ctx, client, server, "istio-system") 74 client.RunAndWait(ctx.Done()) 75 go handlers.Start() 76 77 // label the namespace 78 labelsPatch := []byte(fmt.Sprintf(`{"metadata":{"labels":{"%s":"%s"}}}`, 79 constants.DataplaneModeLabel, constants.DataplaneModeAmbient)) 80 _, err := client.Kube().CoreV1().Namespaces().Patch(ctx, ns.Name, 81 types.MergePatchType, labelsPatch, metav1.PatchOptions{}) 82 assert.NoError(t, err) 83 84 waitForMockCalls() 85 86 assertPodAnnotated(t, client, pod) 87 88 // Assert expected calls actually made 89 fs.AssertExpectations(t) 90 } 91 92 func TestExistingPodAddedWhenDualStack(t *testing.T) { 93 setupLogging() 94 NodeName = "testnode" 95 ctx, cancel := context.WithCancel(context.Background()) 96 defer cancel() 97 98 pod := &corev1.Pod{ 99 ObjectMeta: metav1.ObjectMeta{ 100 Name: "test", 101 Namespace: "test", 102 }, 103 Spec: corev1.PodSpec{ 104 NodeName: NodeName, 105 }, 106 Status: corev1.PodStatus{ 107 PodIPs: []corev1.PodIP{ 108 { 109 IP: "11.1.1.12", 110 }, 111 }, 112 }, 113 } 114 ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test"}} 115 116 client := kube.NewFakeClient(ns, pod) 117 118 // We are expecting at most 1 calls to the mock, wait for them 119 wg, waitForMockCalls := NewWaitForNCalls(t, 1) 120 121 fs := &fakeServer{testWG: wg} 122 123 fs.On("AddPodToMesh", 124 ctx, 125 pod, 126 util.GetPodIPsIfPresent(pod), 127 "", 128 ).Return(nil) 129 130 server := &meshDataplane{ 131 kubeClient: client.Kube(), 132 netServer: fs, 133 } 134 135 fs.Start(ctx) 136 handlers := setupHandlers(ctx, client, server, "istio-system") 137 client.RunAndWait(ctx.Done()) 138 go handlers.Start() 139 140 // label the namespace 141 labelsPatch := []byte(fmt.Sprintf(`{"metadata":{"labels":{"%s":"%s"}}}`, 142 constants.DataplaneModeLabel, constants.DataplaneModeAmbient)) 143 _, err := client.Kube().CoreV1().Namespaces().Patch(ctx, ns.Name, 144 types.MergePatchType, labelsPatch, metav1.PatchOptions{}) 145 assert.NoError(t, err) 146 147 waitForMockCalls() 148 149 assertPodAnnotated(t, client, pod) 150 151 // Assert expected calls actually made 152 fs.AssertExpectations(t) 153 } 154 155 func TestExistingPodNotAddedIfNoIPInAnyStatusField(t *testing.T) { 156 setupLogging() 157 NodeName = "testnode" 158 ctx, cancel := context.WithCancel(context.Background()) 159 defer cancel() 160 161 mt := monitortest.New(t) 162 163 pod := &corev1.Pod{ 164 ObjectMeta: metav1.ObjectMeta{ 165 Name: "test", 166 Namespace: "test", 167 }, 168 Spec: corev1.PodSpec{ 169 NodeName: NodeName, 170 }, 171 Status: corev1.PodStatus{ 172 PodIPs: []corev1.PodIP{}, 173 PodIP: "", 174 }, 175 } 176 ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test"}} 177 178 client := kube.NewFakeClient(ns, pod) 179 180 fs := &fakeServer{} 181 182 server := &meshDataplane{ 183 kubeClient: client.Kube(), 184 netServer: fs, 185 } 186 187 handlers := setupHandlers(ctx, client, server, "istio-system") 188 client.RunAndWait(ctx.Done()) 189 go handlers.Start() 190 191 // label the namespace 192 labelsPatch := []byte(fmt.Sprintf(`{"metadata":{"labels":{"%s":"%s"}}}`, 193 constants.DataplaneModeLabel, constants.DataplaneModeAmbient)) 194 _, err := client.Kube().CoreV1().Namespaces().Patch(ctx, ns.Name, 195 types.MergePatchType, labelsPatch, metav1.PatchOptions{}) 196 assert.NoError(t, err) 197 198 // wait until at least one add event happens 199 mt.Assert(EventTotals.Name(), map[string]string{"type": "add"}, monitortest.AtLeast(1)) 200 201 assertPodNotAnnotated(t, client, pod) 202 203 // Assert expected calls (not) actually made 204 fs.AssertExpectations(t) 205 } 206 207 func TestExistingPodRemovedWhenNsUnlabeled(t *testing.T) { 208 setupLogging() 209 mt := monitortest.New(t) 210 NodeName = "testnode" 211 212 ctx, cancel := context.WithCancel(context.Background()) 213 defer cancel() 214 215 pod := &corev1.Pod{ 216 ObjectMeta: metav1.ObjectMeta{ 217 Name: "test", 218 Namespace: "test", 219 }, 220 Spec: corev1.PodSpec{ 221 NodeName: NodeName, 222 }, 223 Status: corev1.PodStatus{ 224 PodIP: "11.1.1.12", 225 }, 226 } 227 ns := &corev1.Namespace{ 228 ObjectMeta: metav1.ObjectMeta{Name: "test"}, 229 // TODO: once we if the add pod bug, re-enable this and remove the patch below 230 // Labels: map[string]string{constants.DataplaneModeLabel: constants.DataplaneModeAmbient}, 231 232 } 233 234 client := kube.NewFakeClient(ns, pod) 235 236 // We are expecting at most 2 calls to the mock, wait for them 237 wg, waitForMockCalls := NewWaitForNCalls(t, 2) 238 fs := &fakeServer{testWG: wg} 239 240 fs.On("AddPodToMesh", 241 ctx, 242 pod, 243 util.GetPodIPsIfPresent(pod), 244 "", 245 ).Return(nil) 246 247 server := &meshDataplane{ 248 kubeClient: client.Kube(), 249 netServer: fs, 250 } 251 252 handlers := setupHandlers(ctx, client, server, "istio-system") 253 client.RunAndWait(ctx.Done()) 254 go handlers.Start() 255 // wait until pod add was called 256 mt.Assert(EventTotals.Name(), map[string]string{"type": "update"}, monitortest.AtLeast(1)) 257 258 log.Debug("labeling namespace") 259 _, err := client.Kube().CoreV1().Namespaces().Patch(ctx, ns.Name, 260 types.MergePatchType, []byte(fmt.Sprintf(`{"metadata":{"labels":{"%s":"%s"}}}`, 261 constants.DataplaneModeLabel, constants.DataplaneModeAmbient)), metav1.PatchOptions{}) 262 assert.NoError(t, err) 263 264 // wait for an update event 265 mt.Assert(EventTotals.Name(), map[string]string{"type": "update"}, monitortest.AtLeast(2)) 266 267 // wait for the pod to be annotated 268 // after Pod annotated, another update event will be triggered. 269 assertPodAnnotated(t, client, pod) 270 271 // Assert expected calls actually made 272 fs.AssertExpectations(t) 273 274 // unlabelling the namespace should cause only one RemovePodFromMesh to happen 275 fs.On("RemovePodFromMesh", 276 ctx, 277 mock.Anything, 278 ).Once().Return(nil) 279 280 // unlabel the namespace 281 labelsPatch := []byte(fmt.Sprintf(`{"metadata":{"labels":{"%s":null}}}`, 282 constants.DataplaneModeLabel)) 283 _, err = client.Kube().CoreV1().Namespaces().Patch(ctx, ns.Name, 284 types.MergePatchType, labelsPatch, metav1.PatchOptions{}) 285 assert.NoError(t, err) 286 287 // wait for another two update events 288 // total 3 update at before unlabel point: 1. init ns reconcile 2. ns label reconcile 3. pod annotation update 289 mt.Assert(EventTotals.Name(), map[string]string{"type": "update"}, monitortest.AtLeast(5)) 290 291 waitForMockCalls() 292 293 assertPodNotAnnotated(t, client, pod) 294 295 // Assert expected calls actually made 296 fs.AssertExpectations(t) 297 } 298 299 func TestExistingPodRemovedWhenPodAnnotated(t *testing.T) { 300 setupLogging() 301 mt := monitortest.New(t) 302 NodeName = "testnode" 303 304 ctx, cancel := context.WithCancel(context.Background()) 305 defer cancel() 306 307 pod := &corev1.Pod{ 308 ObjectMeta: metav1.ObjectMeta{ 309 Name: "test", 310 Namespace: "test", 311 }, 312 Spec: corev1.PodSpec{ 313 NodeName: NodeName, 314 }, 315 Status: corev1.PodStatus{ 316 PodIP: "11.1.1.12", 317 }, 318 } 319 ns := &corev1.Namespace{ 320 ObjectMeta: metav1.ObjectMeta{Name: "test"}, 321 // TODO: once we if the add pod bug, re-enable this and remove the patch below 322 // Labels: map[string]string{constants.DataplaneModeLabel: constants.DataplaneModeAmbient}, 323 324 } 325 326 client := kube.NewFakeClient(ns, pod) 327 328 // We are expecting at most 2 calls to the mock, wait for them 329 wg, waitForMockCalls := NewWaitForNCalls(t, 2) 330 fs := &fakeServer{testWG: wg} 331 332 fs.On("AddPodToMesh", 333 ctx, 334 pod, 335 util.GetPodIPsIfPresent(pod), 336 "", 337 ).Return(nil) 338 339 server := &meshDataplane{ 340 kubeClient: client.Kube(), 341 netServer: fs, 342 } 343 344 handlers := setupHandlers(ctx, client, server, "istio-system") 345 client.RunAndWait(ctx.Done()) 346 go handlers.Start() 347 // wait until pod add was called 348 mt.Assert(EventTotals.Name(), map[string]string{"type": "update"}, monitortest.AtLeast(1)) 349 350 log.Debug("labeling namespace") 351 _, err := client.Kube().CoreV1().Namespaces().Patch(ctx, ns.Name, 352 types.MergePatchType, []byte(fmt.Sprintf(`{"metadata":{"labels":{"%s":"%s"}}}`, 353 constants.DataplaneModeLabel, constants.DataplaneModeAmbient)), metav1.PatchOptions{}) 354 assert.NoError(t, err) 355 356 // wait for an update event 357 mt.Assert(EventTotals.Name(), map[string]string{"type": "update"}, monitortest.AtLeast(2)) 358 359 // wait for the pod to be annotated 360 // after Pod annotated, another update event will be triggered. 361 assertPodAnnotated(t, client, pod) 362 363 // Assert expected calls actually made 364 fs.AssertExpectations(t) 365 366 // annotate Pod as disabled should cause only one RemovePodFromMesh to happen 367 fs.On("RemovePodFromMesh", 368 ctx, 369 mock.Anything, 370 ).Once().Return(nil) 371 372 // label the pod for exclusion 373 labelsPatch := []byte(fmt.Sprintf(`{"metadata":{"labels":{"%s":"%s"}}}`, 374 constants.DataplaneModeLabel, constants.DataplaneModeNone)) 375 _, err = client.Kube().CoreV1().Pods(pod.Namespace).Patch(ctx, pod.Name, 376 types.MergePatchType, labelsPatch, metav1.PatchOptions{}) 377 assert.NoError(t, err) 378 379 // wait for an update events 380 // total 3 update at before unlabel point: 1. init ns reconcile 2. ns label reconcile 3. pod annotation update 381 mt.Assert(EventTotals.Name(), map[string]string{"type": "update"}, monitortest.AtLeast(4)) 382 383 waitForMockCalls() 384 385 assertPodNotAnnotated(t, client, pod) 386 387 // patch a test label to emulate a POD update event 388 _, err = client.Kube().CoreV1().Pods(pod.Namespace).Patch(ctx, pod.Name, 389 types.MergePatchType, []byte(`{"metadata":{"labels":{"test":"update"}}}`), metav1.PatchOptions{}) 390 assert.NoError(t, err) 391 392 // wait for an update events 393 mt.Assert(EventTotals.Name(), map[string]string{"type": "update"}, monitortest.AtLeast(5)) 394 395 assertPodNotAnnotated(t, client, pod) 396 397 // Assert expected calls actually made 398 fs.AssertExpectations(t) 399 } 400 401 func TestAmbientEnabledReturnsPodIfEnabled(t *testing.T) { 402 setupLogging() 403 NodeName = "testnode" 404 405 ctx, cancel := context.WithCancel(context.Background()) 406 defer cancel() 407 408 pod := &corev1.Pod{ 409 ObjectMeta: metav1.ObjectMeta{ 410 Name: "test", 411 Namespace: "test", 412 UID: "1234", 413 }, 414 Spec: corev1.PodSpec{ 415 NodeName: NodeName, 416 }, 417 Status: corev1.PodStatus{ 418 PodIP: "11.1.1.12", 419 }, 420 } 421 ns := &corev1.Namespace{ 422 ObjectMeta: metav1.ObjectMeta{ 423 Name: "test", 424 Labels: map[string]string{constants.DataplaneModeLabel: constants.DataplaneModeAmbient}, 425 }, 426 } 427 428 client := kube.NewFakeClient(ns, pod) 429 fs := &fakeServer{} 430 fs.Start(ctx) 431 server := &meshDataplane{ 432 kubeClient: client.Kube(), 433 netServer: fs, 434 } 435 436 handlers := setupHandlers(ctx, client, server, "istio-system") 437 client.RunAndWait(ctx.Done()) 438 _, err := handlers.GetPodIfAmbient(pod.Name, ns.Name) 439 440 assert.NoError(t, err) 441 } 442 443 func TestAmbientEnabledReturnsNoPodIfNotEnabled(t *testing.T) { 444 setupLogging() 445 NodeName = "testnode" 446 447 ctx, cancel := context.WithCancel(context.Background()) 448 defer cancel() 449 450 pod := &corev1.Pod{ 451 ObjectMeta: metav1.ObjectMeta{ 452 Name: "test", 453 Namespace: "test", 454 UID: "1234", 455 Labels: map[string]string{constants.DataplaneModeLabel: constants.DataplaneModeNone}, 456 }, 457 Spec: corev1.PodSpec{ 458 NodeName: NodeName, 459 }, 460 Status: corev1.PodStatus{ 461 PodIP: "11.1.1.12", 462 }, 463 } 464 ns := &corev1.Namespace{ 465 ObjectMeta: metav1.ObjectMeta{ 466 Name: "test", 467 Labels: map[string]string{constants.DataplaneModeLabel: constants.DataplaneModeAmbient}, 468 }, 469 } 470 471 client := kube.NewFakeClient(ns, pod) 472 fs := &fakeServer{} 473 fs.Start(ctx) 474 server := &meshDataplane{ 475 kubeClient: client.Kube(), 476 netServer: fs, 477 } 478 479 handlers := setupHandlers(ctx, client, server, "istio-system") 480 client.RunAndWait(ctx.Done()) 481 disabledPod, err := handlers.GetPodIfAmbient(pod.Name, ns.Name) 482 483 assert.NoError(t, err) 484 assert.Equal(t, disabledPod, nil) 485 } 486 487 func TestAmbientEnabledReturnsErrorIfBogusNS(t *testing.T) { 488 setupLogging() 489 NodeName = "testnode" 490 491 ctx, cancel := context.WithCancel(context.Background()) 492 defer cancel() 493 494 pod := &corev1.Pod{ 495 ObjectMeta: metav1.ObjectMeta{ 496 Name: "test", 497 Namespace: "test", 498 UID: "1234", 499 Labels: map[string]string{constants.DataplaneModeLabel: constants.DataplaneModeNone}, 500 }, 501 Spec: corev1.PodSpec{ 502 NodeName: NodeName, 503 }, 504 Status: corev1.PodStatus{ 505 PodIP: "11.1.1.12", 506 }, 507 } 508 ns := &corev1.Namespace{ 509 ObjectMeta: metav1.ObjectMeta{ 510 Name: "test", 511 Labels: map[string]string{constants.DataplaneModeLabel: constants.DataplaneModeAmbient}, 512 }, 513 } 514 515 client := kube.NewFakeClient(ns, pod) 516 fs := &fakeServer{} 517 fs.Start(ctx) 518 server := &meshDataplane{ 519 kubeClient: client.Kube(), 520 netServer: fs, 521 } 522 523 handlers := setupHandlers(ctx, client, server, "istio-system") 524 client.RunAndWait(ctx.Done()) 525 disabledPod, err := handlers.GetPodIfAmbient(pod.Name, "what") 526 527 assert.Error(t, err) 528 assert.Equal(t, disabledPod, nil) 529 } 530 531 func TestExistingPodAddedWhenItPreExists(t *testing.T) { 532 setupLogging() 533 NodeName = "testnode" 534 535 mt := monitortest.New(t) 536 537 ctx, cancel := context.WithCancel(context.Background()) 538 defer cancel() 539 540 pod := &corev1.Pod{ 541 ObjectMeta: metav1.ObjectMeta{ 542 Name: "test", 543 Namespace: "test", 544 }, 545 Spec: corev1.PodSpec{ 546 NodeName: NodeName, 547 }, 548 Status: corev1.PodStatus{ 549 PodIP: "11.1.1.12", 550 }, 551 } 552 ns := &corev1.Namespace{ 553 ObjectMeta: metav1.ObjectMeta{ 554 Name: "test", 555 Labels: map[string]string{constants.DataplaneModeLabel: constants.DataplaneModeAmbient}, 556 }, 557 } 558 559 client := kube.NewFakeClient(ns, pod) 560 561 // We are expecting at most 1 calls to the mock, wait for them 562 wg, waitForMockCalls := NewWaitForNCalls(t, 1) 563 fs := &fakeServer{testWG: wg} 564 565 fs.On("AddPodToMesh", 566 ctx, 567 pod, 568 util.GetPodIPsIfPresent(pod), 569 "", 570 ).Return(nil) 571 572 server := &meshDataplane{ 573 kubeClient: client.Kube(), 574 netServer: fs, 575 } 576 577 handlers := setupHandlers(ctx, client, server, "istio-system") 578 client.RunAndWait(ctx.Done()) 579 go handlers.Start() 580 581 waitForMockCalls() 582 // wait until pod add was called 583 mt.Assert(EventTotals.Name(), map[string]string{"type": "add"}, monitortest.AtLeast(1)) 584 585 assertPodAnnotated(t, client, pod) 586 587 // check expectations on mocked calls 588 fs.AssertExpectations(t) 589 } 590 591 func assertPodAnnotated(t *testing.T, client kube.Client, pod *corev1.Pod) { 592 for i := 0; i < 5; i++ { 593 p, err := client.Kube().CoreV1().Pods(pod.Namespace).Get(context.Background(), pod.Name, metav1.GetOptions{}) 594 if err != nil { 595 t.Fatal(err) 596 } 597 if p.Annotations[constants.AmbientRedirection] == constants.AmbientRedirectionEnabled { 598 return 599 } 600 time.Sleep(1 * time.Second) 601 } 602 t.Fatal("Pod not annotated") 603 } 604 605 func assertPodNotAnnotated(t *testing.T, client kube.Client, pod *corev1.Pod) { 606 for i := 0; i < 5; i++ { 607 p, err := client.Kube().CoreV1().Pods(pod.Namespace).Get(context.Background(), pod.Name, metav1.GetOptions{}) 608 if err != nil { 609 t.Fatal(err) 610 } 611 if p.Annotations[constants.AmbientRedirection] != constants.AmbientRedirectionEnabled { 612 return 613 } 614 time.Sleep(1 * time.Second) 615 } 616 t.Fatal("Pod annotated") 617 }