istio.io/istio@v0.0.0-20240520182934-d79c90f27776/cni/pkg/nodeagent/net_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 "errors" 20 "net/netip" 21 "runtime" 22 "sync/atomic" 23 "testing" 24 "time" 25 26 "golang.org/x/sys/unix" 27 corev1 "k8s.io/api/core/v1" 28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 "k8s.io/apimachinery/pkg/types" 30 "k8s.io/apimachinery/pkg/util/intstr" 31 32 "istio.io/istio/cni/pkg/ipset" 33 "istio.io/istio/cni/pkg/iptables" 34 istiolog "istio.io/istio/pkg/log" 35 "istio.io/istio/pkg/test/util/assert" 36 "istio.io/istio/tools/istio-iptables/pkg/dependencies" 37 ) 38 39 func setupLogging() { 40 opts := istiolog.DefaultOptions() 41 opts.SetDefaultOutputLevel(istiolog.OverrideScopeName, istiolog.DebugLevel) 42 istiolog.Configure(opts) 43 for _, scope := range istiolog.Scopes() { 44 scope.SetOutputLevel(istiolog.DebugLevel) 45 } 46 } 47 48 type netTestFixture struct { 49 netServer *NetServer 50 podNsMap *podNetnsCache 51 ztunnelServer *fakeZtunnel 52 iptablesConfigurator *iptables.IptablesConfigurator 53 nlDeps *fakeIptablesDeps 54 ipsetDeps *ipset.MockedIpsetDeps 55 } 56 57 func getTestFixure(ctx context.Context) netTestFixture { 58 podNsMap := newPodNetnsCache(openNsTestOverride) 59 nlDeps := &fakeIptablesDeps{} 60 iptablesConfigurator, _ := iptables.NewIptablesConfigurator(nil, &dependencies.DependenciesStub{}, nlDeps) 61 62 ztunnelServer := &fakeZtunnel{} 63 64 fakeIPSetDeps := ipset.FakeNLDeps() 65 set := ipset.IPSet{V4Name: "foo-v4", Prefix: "foo", Deps: fakeIPSetDeps} 66 netServer := newNetServer(ztunnelServer, podNsMap, iptablesConfigurator, NewPodNetnsProcFinder(fakeFs()), set) 67 68 netServer.netnsRunner = func(fdable NetnsFd, toRun func() error) error { 69 return toRun() 70 } 71 netServer.Start(ctx) 72 return netTestFixture{ 73 netServer: netServer, 74 podNsMap: podNsMap, 75 ztunnelServer: ztunnelServer, 76 iptablesConfigurator: iptablesConfigurator, 77 nlDeps: nlDeps, 78 ipsetDeps: fakeIPSetDeps, 79 } 80 } 81 82 func buildConvincingPod(v6IP bool) *corev1.Pod { 83 app1 := corev1.Container{ 84 Name: "app1", 85 Ports: []corev1.ContainerPort{ 86 { 87 Name: "foo-port", 88 ContainerPort: 8010, 89 }, 90 { 91 Name: "foo-2-port", 92 ContainerPort: 8020, 93 }, 94 }, 95 LivenessProbe: &corev1.Probe{ 96 ProbeHandler: corev1.ProbeHandler{ 97 HTTPGet: &corev1.HTTPGetAction{ 98 Port: intstr.FromString("foo-2-port"), 99 }, 100 }, 101 }, 102 StartupProbe: &corev1.Probe{ 103 ProbeHandler: corev1.ProbeHandler{ 104 HTTPGet: &corev1.HTTPGetAction{ 105 Port: intstr.FromInt(7777), 106 }, 107 }, 108 }, 109 } 110 111 containers := []corev1.Container{app1} 112 113 var podStatus corev1.PodStatus 114 if v6IP { 115 podStatus = corev1.PodStatus{ 116 PodIP: "e9ac:1e77:90ca:399f:4d6d:ece2:2f9b:3164", 117 PodIPs: []corev1.PodIP{{IP: "e9ac:1e77:90ca:399f:4d6d:ece2:2f9b:3164"}, {IP: "e9ac:1e77:90ca:399f:4d6d:ece2:2f9b:3165"}}, 118 } 119 } else { 120 podStatus = corev1.PodStatus{ 121 PodIP: "2.2.2.2", 122 PodIPs: []corev1.PodIP{{IP: "2.2.2.2"}, {IP: "3.3.3.3"}}, 123 } 124 } 125 126 return &corev1.Pod{ 127 ObjectMeta: metav1.ObjectMeta{ 128 Name: "foo", 129 Namespace: "bar", 130 UID: "123", 131 }, 132 Spec: corev1.PodSpec{ 133 Containers: containers, 134 }, 135 Status: podStatus, 136 } 137 } 138 139 func TestServerAddPod(t *testing.T) { 140 ctx, cancel := context.WithCancel(context.Background()) 141 defer cancel() 142 setupLogging() 143 fixture := getTestFixure(ctx) 144 netServer := fixture.netServer 145 ztunnelServer := fixture.ztunnelServer 146 podMeta := metav1.ObjectMeta{ 147 Name: "foo", 148 Namespace: "bar", 149 UID: "123", 150 } 151 podIP := netip.MustParseAddr("99.9.9.9") 152 podIPs := []netip.Addr{podIP} 153 154 fixture.ipsetDeps.On("addIP", 155 "foo-v4", 156 netip.MustParseAddr("99.9.9.9"), 157 uint8(unix.IPPROTO_TCP), 158 string(podMeta.UID), 159 false, 160 ).Return(nil) 161 162 err := netServer.AddPodToMesh(ctx, &corev1.Pod{ObjectMeta: podMeta}, podIPs, "fakenetns") 163 assert.NoError(t, err) 164 assert.Equal(t, 1, ztunnelServer.addedPods.Load()) 165 } 166 167 func TestServerRemovePod(t *testing.T) { 168 ctx, cancel := context.WithCancel(context.Background()) 169 defer cancel() 170 setupLogging() 171 fixture := getTestFixure(ctx) 172 netServer := fixture.netServer 173 ztunnelServer := fixture.ztunnelServer 174 nlDeps := fixture.nlDeps 175 pod := &corev1.Pod{ 176 ObjectMeta: metav1.ObjectMeta{ 177 Name: "foo", 178 Namespace: "bar", 179 UID: "123", 180 }, 181 Spec: corev1.PodSpec{ServiceAccountName: "sa"}, 182 } 183 184 // this is usually called after add. so manually add the pod uid for now 185 fakens := newFakeNs(123) 186 closed := fakens.closed 187 workload := WorkloadInfo{ 188 Workload: podToWorkload(pod), 189 Netns: fakens, 190 } 191 fixture.podNsMap.UpsertPodCacheWithNetns(string(pod.UID), workload) 192 err := netServer.RemovePodFromMesh(ctx, pod) 193 assert.NoError(t, err) 194 assert.Equal(t, ztunnelServer.deletedPods.Load(), 1) 195 assert.Equal(t, nlDeps.DelInpodMarkIPRuleCnt.Load(), 1) 196 assert.Equal(t, nlDeps.DelLoopbackRoutesCnt.Load(), 1) 197 // make sure the uid was taken from cache and netns closed 198 netns := fixture.podNsMap.Take(string(pod.UID)) 199 assert.Equal(t, nil, netns) 200 201 // run gc to clean up ns: 202 203 //revive:disable-next-line:call-to-gc Just a test that we are cleaning up the netns 204 runtime.GC() 205 assertNSClosed(t, closed) 206 } 207 208 func TestServerDeletePod(t *testing.T) { 209 ctx, cancel := context.WithCancel(context.Background()) 210 defer cancel() 211 setupLogging() 212 fixture := getTestFixure(ctx) 213 netServer := fixture.netServer 214 ztunnelServer := fixture.ztunnelServer 215 nlDeps := fixture.nlDeps 216 pod := &corev1.Pod{ 217 ObjectMeta: metav1.ObjectMeta{ 218 Name: "foo", 219 Namespace: "bar", 220 UID: "123", 221 }, 222 Spec: corev1.PodSpec{ServiceAccountName: "sa"}, 223 } 224 225 // this is usually called after add. so manually add the pod uid for now 226 fakens := newFakeNs(123) 227 closed := fakens.closed 228 workload := WorkloadInfo{ 229 Workload: podToWorkload(pod), 230 Netns: fakens, 231 } 232 fixture.podNsMap.UpsertPodCacheWithNetns(string(pod.UID), workload) 233 err := netServer.DelPodFromMesh(ctx, pod) 234 assert.NoError(t, err) 235 assert.Equal(t, ztunnelServer.deletedPods.Load(), 1) 236 // with delete iptables is not called, as there is no need to delete the iptables rules 237 // from a pod that's gone from the cluster. 238 assert.Equal(t, nlDeps.DelInpodMarkIPRuleCnt.Load(), 0) 239 assert.Equal(t, nlDeps.DelLoopbackRoutesCnt.Load(), 0) 240 // make sure the uid was taken from cache and netns closed 241 netns := fixture.podNsMap.Take(string(pod.UID)) 242 assert.Equal(t, nil, netns) 243 // run gc to clean up ns: 244 245 //revive:disable-next-line:call-to-gc Just a test that we are cleaning up the netns 246 runtime.GC() 247 assertNSClosed(t, closed) 248 } 249 250 func expectPodAddedToIPSet(ipsetDeps *ipset.MockedIpsetDeps, podMeta metav1.ObjectMeta) { 251 ipsetDeps.On("addIP", 252 "foo-v4", 253 netip.MustParseAddr("99.9.9.9"), 254 uint8(unix.IPPROTO_TCP), 255 string(podMeta.UID), 256 false, 257 ).Return(nil) 258 } 259 260 func TestServerAddPodWithNoNetns(t *testing.T) { 261 ctx, cancel := context.WithCancel(context.Background()) 262 defer cancel() 263 setupLogging() 264 fixture := getTestFixure(ctx) 265 netServer := fixture.netServer 266 ztunnelServer := fixture.ztunnelServer 267 podMeta := metav1.ObjectMeta{ 268 Name: "foo", 269 Namespace: "bar", 270 // this uid exists in the fake filesystem. 271 UID: "863b91d4-4b68-4efa-917f-4b560e3e86aa", 272 } 273 podIP := netip.MustParseAddr("99.9.9.9") 274 podIPs := []netip.Addr{podIP} 275 expectPodAddedToIPSet(fixture.ipsetDeps, podMeta) 276 277 err := netServer.AddPodToMesh(ctx, &corev1.Pod{ObjectMeta: podMeta}, podIPs, "") 278 assert.NoError(t, err) 279 assert.Equal(t, ztunnelServer.addedPods.Load(), 1) 280 } 281 282 func TestReturnsPartialErrorOnZtunnelFail(t *testing.T) { 283 ctx, cancel := context.WithCancel(context.Background()) 284 defer cancel() 285 setupLogging() 286 fixture := getTestFixure(ctx) 287 netServer := fixture.netServer 288 ztunnelServer := fixture.ztunnelServer 289 290 podMeta := metav1.ObjectMeta{ 291 Name: "foo", 292 Namespace: "bar", 293 UID: "123", 294 } 295 ztunnelServer.addError = errors.New("fake error") 296 podIP := netip.MustParseAddr("99.9.9.9") 297 podIPs := []netip.Addr{podIP} 298 299 expectPodAddedToIPSet(fixture.ipsetDeps, podMeta) 300 err := netServer.AddPodToMesh(ctx, &corev1.Pod{ObjectMeta: podMeta}, podIPs, "faksens") 301 assert.Equal(t, ztunnelServer.addedPods.Load(), 1) 302 if !errors.Is(err, ErrPartialAdd) { 303 t.Fatal("expected partial error") 304 } 305 } 306 307 func TestDoesntReturnsPartialErrorOnIptablesFail(t *testing.T) { 308 ctx, cancel := context.WithCancel(context.Background()) 309 defer cancel() 310 setupLogging() 311 fixture := getTestFixure(ctx) 312 netServer := fixture.netServer 313 ztunnelServer := fixture.ztunnelServer 314 nlDeps := fixture.nlDeps 315 nlDeps.AddRouteErr = errors.New("fake error") 316 317 podMeta := metav1.ObjectMeta{ 318 Name: "foo", 319 Namespace: "bar", 320 UID: "123", 321 } 322 podIP := netip.MustParseAddr("99.9.9.9") 323 podIPs := []netip.Addr{podIP} 324 325 expectPodAddedToIPSet(fixture.ipsetDeps, podMeta) 326 err := netServer.AddPodToMesh(ctx, &corev1.Pod{ObjectMeta: podMeta}, podIPs, "faksens") 327 // no calls to ztunnel if iptables failed 328 assert.Equal(t, ztunnelServer.addedPods.Load(), 0) 329 330 // error is not partial error 331 if errors.Is(err, ErrPartialAdd) { 332 t.Fatal("expected not a partial error") 333 } 334 } 335 336 func TestConstructInitialSnap(t *testing.T) { 337 ctx, cancel := context.WithCancel(context.Background()) 338 defer cancel() 339 setupLogging() 340 fixture := getTestFixure(ctx) 341 netServer := fixture.netServer 342 343 podMeta := metav1.ObjectMeta{ 344 Name: "foo", 345 Namespace: "bar", 346 UID: types.UID("863b91d4-4b68-4efa-917f-4b560e3e86aa"), 347 } 348 pod := &corev1.Pod{ObjectMeta: podMeta} 349 350 fixture.ipsetDeps.On("listEntriesByIP", 351 "foo-v4", 352 ).Return([]netip.Addr{}, nil) 353 354 err := netServer.ConstructInitialSnapshot([]*corev1.Pod{pod}) 355 assert.NoError(t, err) 356 if fixture.podNsMap.Get("863b91d4-4b68-4efa-917f-4b560e3e86aa") == nil { 357 t.Fatal("expected pod to be in cache") 358 } 359 } 360 361 func TestAddPodToHostNSIPSets(t *testing.T) { 362 pod := buildConvincingPod(false) 363 364 var podUID string = string(pod.ObjectMeta.UID) 365 fakeIPSetDeps := ipset.FakeNLDeps() 366 set := ipset.IPSet{V4Name: "foo-v4", Prefix: "foo", Deps: fakeIPSetDeps} 367 ipProto := uint8(unix.IPPROTO_TCP) 368 369 fakeIPSetDeps.On("addIP", 370 "foo-v4", 371 netip.MustParseAddr("99.9.9.9"), 372 ipProto, 373 podUID, 374 false, 375 ).Return(nil) 376 377 fakeIPSetDeps.On("addIP", 378 "foo-v4", 379 netip.MustParseAddr("2.2.2.2"), 380 ipProto, 381 podUID, 382 false, 383 ).Return(nil) 384 385 podIPs := []netip.Addr{netip.MustParseAddr("99.9.9.9"), netip.MustParseAddr("2.2.2.2")} 386 err := addPodToHostNSIpset(pod, podIPs, &set) 387 assert.NoError(t, err) 388 389 fakeIPSetDeps.AssertExpectations(t) 390 } 391 392 func TestAddPodToHostNSIPSetsV6(t *testing.T) { 393 pod := buildConvincingPod(true) 394 395 var podUID string = string(pod.ObjectMeta.UID) 396 fakeIPSetDeps := ipset.FakeNLDeps() 397 set := ipset.IPSet{V4Name: "foo-v4", V6Name: "foo-v6", Prefix: "foo", Deps: fakeIPSetDeps} 398 ipProto := uint8(unix.IPPROTO_TCP) 399 400 fakeIPSetDeps.On("addIP", 401 "foo-v6", 402 netip.MustParseAddr("e9ac:1e77:90ca:399f:4d6d:ece3:2f9b:3162"), 403 ipProto, 404 podUID, 405 false, 406 ).Return(nil) 407 408 fakeIPSetDeps.On("addIP", 409 "foo-v6", 410 netip.MustParseAddr("e9ac:1e77:90ca:399f:4d6d:ece2:2f9b:3164"), 411 ipProto, 412 podUID, 413 false, 414 ).Return(nil) 415 416 podIPs := []netip.Addr{netip.MustParseAddr("e9ac:1e77:90ca:399f:4d6d:ece3:2f9b:3162"), netip.MustParseAddr("e9ac:1e77:90ca:399f:4d6d:ece2:2f9b:3164")} 417 err := addPodToHostNSIpset(pod, podIPs, &set) 418 assert.NoError(t, err) 419 420 fakeIPSetDeps.AssertExpectations(t) 421 } 422 423 func TestAddPodToHostNSIPSetsDualstack(t *testing.T) { 424 pod := buildConvincingPod(true) 425 426 var podUID string = string(pod.ObjectMeta.UID) 427 fakeIPSetDeps := ipset.FakeNLDeps() 428 set := ipset.IPSet{V4Name: "foo-v4", V6Name: "foo-v6", Prefix: "foo", Deps: fakeIPSetDeps} 429 ipProto := uint8(unix.IPPROTO_TCP) 430 431 fakeIPSetDeps.On("addIP", 432 "foo-v6", 433 netip.MustParseAddr("e9ac:1e77:90ca:399f:4d6d:ece3:2f9b:3162"), 434 ipProto, 435 podUID, 436 false, 437 ).Return(nil) 438 439 fakeIPSetDeps.On("addIP", 440 "foo-v4", 441 netip.MustParseAddr("99.9.9.9"), 442 ipProto, 443 podUID, 444 false, 445 ).Return(nil) 446 447 podIPs := []netip.Addr{netip.MustParseAddr("e9ac:1e77:90ca:399f:4d6d:ece3:2f9b:3162"), netip.MustParseAddr("99.9.9.9")} 448 err := addPodToHostNSIpset(pod, podIPs, &set) 449 assert.NoError(t, err) 450 451 fakeIPSetDeps.AssertExpectations(t) 452 } 453 454 func TestAddPodIPToHostNSIPSetsReturnsErrorIfOneFails(t *testing.T) { 455 pod := buildConvincingPod(false) 456 457 var podUID string = string(pod.ObjectMeta.UID) 458 fakeIPSetDeps := ipset.FakeNLDeps() 459 set := ipset.IPSet{V4Name: "foo-v4", Prefix: "foo", Deps: fakeIPSetDeps} 460 ipProto := uint8(unix.IPPROTO_TCP) 461 462 fakeIPSetDeps.On("addIP", 463 "foo-v4", 464 netip.MustParseAddr("99.9.9.9"), 465 ipProto, 466 podUID, 467 false, 468 ).Return(nil) 469 470 fakeIPSetDeps.On("addIP", 471 "foo-v4", 472 netip.MustParseAddr("2.2.2.2"), 473 ipProto, 474 podUID, 475 false, 476 ).Return(errors.New("bwoah")) 477 478 podIPs := []netip.Addr{netip.MustParseAddr("99.9.9.9"), netip.MustParseAddr("2.2.2.2")} 479 480 err := addPodToHostNSIpset(pod, podIPs, &set) 481 assert.Error(t, err) 482 483 fakeIPSetDeps.AssertExpectations(t) 484 } 485 486 func TestRemovePodIPFromHostNSIPSets(t *testing.T) { 487 pod := buildConvincingPod(false) 488 489 fakeIPSetDeps := ipset.FakeNLDeps() 490 set := ipset.IPSet{V4Name: "foo-v4", Prefix: "foo", Deps: fakeIPSetDeps} 491 492 fakeIPSetDeps.On("clearEntriesWithIP", 493 "foo-v4", 494 netip.MustParseAddr("3.3.3.3"), 495 ).Return(nil) 496 497 fakeIPSetDeps.On("clearEntriesWithIP", 498 "foo-v4", 499 netip.MustParseAddr("2.2.2.2"), 500 ).Return(nil) 501 502 err := removePodFromHostNSIpset(pod, &set) 503 assert.NoError(t, err) 504 fakeIPSetDeps.AssertExpectations(t) 505 } 506 507 func TestSyncHostIPSetsPrunesNothingIfNoExtras(t *testing.T) { 508 pod := buildConvincingPod(false) 509 510 fakeIPSetDeps := ipset.FakeNLDeps() 511 512 var podUID string = string(pod.ObjectMeta.UID) 513 ipProto := uint8(unix.IPPROTO_TCP) 514 ctx, cancel := context.WithCancel(context.Background()) 515 fixture := getTestFixure(ctx) 516 defer cancel() 517 setupLogging() 518 519 // expectations 520 fixture.ipsetDeps.On("addIP", 521 "foo-v4", 522 netip.MustParseAddr("3.3.3.3"), 523 ipProto, 524 podUID, 525 false, 526 ).Return(nil) 527 528 fixture.ipsetDeps.On("addIP", 529 "foo-v4", 530 netip.MustParseAddr("2.2.2.2"), 531 ipProto, 532 podUID, 533 false, 534 ).Return(nil) 535 536 fixture.ipsetDeps.On("listEntriesByIP", 537 "foo-v4", 538 ).Return([]netip.Addr{}, nil) 539 540 netServer := fixture.netServer 541 err := netServer.syncHostIPSets([]*corev1.Pod{pod}) 542 assert.NoError(t, err) 543 fakeIPSetDeps.AssertExpectations(t) 544 } 545 546 func TestSyncHostIPSetsAddsNothingIfPodHasNoIPs(t *testing.T) { 547 pod := buildConvincingPod(false) 548 549 pod.Status.PodIP = "" 550 pod.Status.PodIPs = []corev1.PodIP{} 551 552 fakeIPSetDeps := ipset.FakeNLDeps() 553 554 ctx, cancel := context.WithCancel(context.Background()) 555 fixture := getTestFixure(ctx) 556 defer cancel() 557 setupLogging() 558 559 fixture.ipsetDeps.On("listEntriesByIP", 560 "foo-v4", 561 ).Return([]netip.Addr{}, nil) 562 563 netServer := fixture.netServer 564 err := netServer.syncHostIPSets([]*corev1.Pod{pod}) 565 assert.NoError(t, err) 566 fakeIPSetDeps.AssertExpectations(t) 567 } 568 569 func TestSyncHostIPSetsPrunesIfExtras(t *testing.T) { 570 pod := buildConvincingPod(false) 571 572 fakeIPSetDeps := ipset.FakeNLDeps() 573 574 var podUID string = string(pod.ObjectMeta.UID) 575 ipProto := uint8(unix.IPPROTO_TCP) 576 ctx, cancel := context.WithCancel(context.Background()) 577 fixture := getTestFixure(ctx) 578 defer cancel() 579 setupLogging() 580 581 // expectations 582 fixture.ipsetDeps.On("addIP", 583 "foo-v4", 584 netip.MustParseAddr("3.3.3.3"), 585 ipProto, 586 podUID, 587 false, 588 ).Return(nil) 589 590 fixture.ipsetDeps.On("addIP", 591 "foo-v4", 592 netip.MustParseAddr("2.2.2.2"), 593 ipProto, 594 podUID, 595 false, 596 ).Return(nil) 597 598 // List should return one IP not in our "pod snapshot", which means we prune 599 fixture.ipsetDeps.On("listEntriesByIP", 600 "foo-v4", 601 ).Return([]netip.Addr{ 602 netip.MustParseAddr("2.2.2.2"), 603 netip.MustParseAddr("6.6.6.6"), 604 netip.MustParseAddr("3.3.3.3"), 605 }, nil) 606 607 fixture.ipsetDeps.On("clearEntriesWithIP", 608 "foo-v4", 609 netip.MustParseAddr("6.6.6.6"), 610 ).Return(nil) 611 612 netServer := fixture.netServer 613 err := netServer.syncHostIPSets([]*corev1.Pod{pod}) 614 assert.NoError(t, err) 615 fakeIPSetDeps.AssertExpectations(t) 616 } 617 618 // for tests that call `runtime.GC()` - we have no control over when the GC is actually scheduled, 619 // and it is flake-prone to check for closure after calling it, this retries for a bit to make 620 // sure the netns is closed eventually. 621 func assertNSClosed(t *testing.T, closed *atomic.Bool) { 622 for i := 0; i < 5; i++ { 623 if closed.Load() { 624 return 625 } 626 time.Sleep(1 * time.Second) 627 } 628 t.Fatal("NS not closed") 629 }