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  }