github.com/cilium/cilium@v1.16.2/pkg/l2announcer/l2announcer_test.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package l2announcer
     5  
     6  import (
     7  	"context"
     8  	"net/netip"
     9  	"slices"
    10  	"strings"
    11  	"testing"
    12  	"time"
    13  
    14  	"github.com/cilium/hive/cell"
    15  	"github.com/cilium/hive/hivetest"
    16  	"github.com/cilium/hive/job"
    17  	"github.com/cilium/statedb"
    18  	"github.com/sirupsen/logrus"
    19  	"github.com/stretchr/testify/assert"
    20  	"github.com/stretchr/testify/require"
    21  	"go.uber.org/goleak"
    22  	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    23  	"k8s.io/apimachinery/pkg/runtime"
    24  	"k8s.io/client-go/kubernetes/fake"
    25  	"k8s.io/client-go/tools/cache"
    26  	"k8s.io/utils/ptr"
    27  
    28  	"github.com/cilium/cilium/daemon/k8s"
    29  	"github.com/cilium/cilium/pkg/datapath/tables"
    30  	"github.com/cilium/cilium/pkg/hive"
    31  	v2 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2"
    32  	"github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2alpha1"
    33  	"github.com/cilium/cilium/pkg/k8s/client"
    34  	"github.com/cilium/cilium/pkg/k8s/resource"
    35  	slim_corev1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/api/core/v1"
    36  	slim_meta_v1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/apis/meta/v1"
    37  	"github.com/cilium/cilium/pkg/lock"
    38  	"github.com/cilium/cilium/pkg/option"
    39  )
    40  
    41  type fixture struct {
    42  	announcer          *L2Announcer
    43  	proxyNeighborTable statedb.Table[*tables.L2AnnounceEntry]
    44  	stateDB            *statedb.DB
    45  	fakeSvcStore       *fakeStore[*slim_corev1.Service]
    46  	fakePolicyStore    *fakeStore[*v2alpha1.CiliumL2AnnouncementPolicy]
    47  }
    48  
    49  func newFixture(t testing.TB) *fixture {
    50  	var (
    51  		tbl statedb.RWTable[*tables.L2AnnounceEntry]
    52  		db  *statedb.DB
    53  		jr  job.Registry
    54  		jg  job.Group
    55  		h   cell.Health
    56  	)
    57  
    58  	hive.New(
    59  		cell.Provide(tables.NewL2AnnounceTable),
    60  		cell.Module("test", "test", cell.Invoke(func(d *statedb.DB, t statedb.RWTable[*tables.L2AnnounceEntry], h_ cell.Health, j job.Registry, jg_ job.Group) {
    61  			d.RegisterTable(t)
    62  			db = d
    63  			tbl = t
    64  			jr = j
    65  			jg = jg_
    66  			h = h_
    67  		})),
    68  	).Populate(hivetest.Logger(t))
    69  
    70  	fakeSvcStore := &fakeStore[*slim_corev1.Service]{}
    71  	fakePolicyStore := &fakeStore[*v2alpha1.CiliumL2AnnouncementPolicy]{}
    72  
    73  	params := l2AnnouncerParams{
    74  		Logger:    logrus.New(),
    75  		Lifecycle: &cell.DefaultLifecycle{},
    76  		Health:    h,
    77  		DaemonConfig: &option.DaemonConfig{
    78  			ConfigPatchMutex:         new(lock.RWMutex),
    79  			K8sNamespace:             "kube_system",
    80  			EnableL2Announcements:    true,
    81  			L2AnnouncerLeaseDuration: 15 * time.Second,
    82  			L2AnnouncerRenewDeadline: 5 * time.Second,
    83  			L2AnnouncerRetryPeriod:   2 * time.Second,
    84  		},
    85  		Clientset: &client.FakeClientset{
    86  			KubernetesFakeClientset: fake.NewSimpleClientset(),
    87  		},
    88  		L2AnnounceTable: tbl,
    89  		StateDB:         db,
    90  		JobGroup:        jg,
    91  	}
    92  
    93  	// Setting stores normally happens in .run which we bypass for testing purposes
    94  	announcer := NewL2Announcer(params)
    95  	announcer.policyStore = fakePolicyStore
    96  	announcer.svcStore = fakeSvcStore
    97  	announcer.params.JobGroup = jr.NewGroup(h)
    98  	announcer.scopedGroup = announcer.params.JobGroup.Scoped("leader-election")
    99  	announcer.params.JobGroup.Start(context.Background())
   100  
   101  	return &fixture{
   102  		announcer:          announcer,
   103  		proxyNeighborTable: tbl,
   104  		stateDB:            db,
   105  		fakeSvcStore:       fakeSvcStore,
   106  		fakePolicyStore:    fakePolicyStore,
   107  	}
   108  }
   109  
   110  var _ resource.Store[runtime.Object] = (*fakeStore[runtime.Object])(nil)
   111  
   112  type fakeStore[T runtime.Object] struct {
   113  	slice []T
   114  }
   115  
   116  func (fs *fakeStore[T]) List() []T {
   117  	return fs.slice
   118  }
   119  func (fs *fakeStore[T]) IterKeys() resource.KeyIter { return nil }
   120  func (fs *fakeStore[T]) Get(obj T) (item T, exists bool, err error) {
   121  	var def T
   122  	return def, false, nil
   123  }
   124  func (fs *fakeStore[T]) GetByKey(key resource.Key) (item T, exists bool, err error) {
   125  	var def T
   126  	return def, false, nil
   127  }
   128  func (fs *fakeStore[T]) IndexKeys(indexName, indexedValue string) ([]string, error) {
   129  	return nil, nil
   130  }
   131  func (fs *fakeStore[T]) ByIndex(indexName, indexedValue string) ([]T, error) {
   132  	return nil, nil
   133  }
   134  func (fs *fakeStore[T]) CacheStore() cache.Store { return nil }
   135  func (fs *fakeStore[T]) Release()                {}
   136  
   137  var _ resource.Resource[runtime.Object] = (*fakeResource[runtime.Object])(nil)
   138  
   139  type fakeResource[T runtime.Object] struct {
   140  	store resource.Store[T]
   141  }
   142  
   143  func (fr *fakeResource[T]) Observe(ctx context.Context, next func(event resource.Event[T]), complete func(error)) {
   144  
   145  }
   146  
   147  func (fr *fakeResource[T]) Events(ctx context.Context, opts ...resource.EventsOpt) <-chan resource.Event[T] {
   148  	return make(<-chan resource.Event[T])
   149  }
   150  
   151  func (fr *fakeResource[T]) Store(context.Context) (resource.Store[T], error) {
   152  	if fr.store != nil {
   153  		return fr.store, nil
   154  	}
   155  
   156  	return &fakeStore[T]{}, nil
   157  }
   158  
   159  func blueNode() *v2.CiliumNode {
   160  	return &v2.CiliumNode{
   161  		ObjectMeta: meta_v1.ObjectMeta{
   162  			Name: "blue-node",
   163  			Labels: map[string]string{
   164  				"color": "blue",
   165  			},
   166  		},
   167  	}
   168  }
   169  
   170  func bluePolicy() *v2alpha1.CiliumL2AnnouncementPolicy {
   171  	return &v2alpha1.CiliumL2AnnouncementPolicy{
   172  		ObjectMeta: meta_v1.ObjectMeta{
   173  			Name: "blue-policy",
   174  		},
   175  		Spec: v2alpha1.CiliumL2AnnouncementPolicySpec{
   176  			NodeSelector: &slim_meta_v1.LabelSelector{
   177  				MatchLabels: map[string]string{
   178  					"color": "blue",
   179  				},
   180  			},
   181  			ServiceSelector: &slim_meta_v1.LabelSelector{
   182  				MatchLabels: map[string]string{
   183  					"color": "blue",
   184  				},
   185  			},
   186  			ExternalIPs: true,
   187  			Interfaces: []string{
   188  				"eno01",
   189  			},
   190  		},
   191  	}
   192  }
   193  
   194  func blueService() *slim_corev1.Service {
   195  	return &slim_corev1.Service{
   196  		ObjectMeta: slim_meta_v1.ObjectMeta{
   197  			Namespace: "default",
   198  			Name:      "blue-service",
   199  			Labels: map[string]string{
   200  				"color": "blue",
   201  			},
   202  		},
   203  		Spec: slim_corev1.ServiceSpec{
   204  			ExternalIPs: []string{"192.168.2.1"},
   205  		},
   206  	}
   207  }
   208  
   209  // Test the happy path, make sure that we create proxy neighbor entries
   210  func TestHappyPath(t *testing.T) {
   211  	fix := newFixture(t)
   212  
   213  	fix.announcer.devices = []string{"eno01"}
   214  	err := fix.announcer.processDevicesChanged(context.Background())
   215  	assert.NoError(t, err)
   216  
   217  	localNode := blueNode()
   218  	err = fix.announcer.upsertLocalNode(context.Background(), localNode)
   219  	assert.NoError(t, err)
   220  	assert.Equal(t, localNode, fix.announcer.localNode)
   221  
   222  	policy := bluePolicy()
   223  	fix.fakePolicyStore.slice = append(fix.fakePolicyStore.slice, policy)
   224  	err = fix.announcer.processPolicyEvent(context.Background(), resource.Event[*v2alpha1.CiliumL2AnnouncementPolicy]{
   225  		Kind:   resource.Upsert,
   226  		Key:    resource.NewKey(policy),
   227  		Object: policy,
   228  		Done:   func(err error) {},
   229  	})
   230  	assert.NoError(t, err)
   231  	assert.Contains(t, fix.announcer.selectedPolicies, resource.NewKey(policy))
   232  
   233  	svc := blueService()
   234  	fix.fakeSvcStore.slice = append(fix.fakeSvcStore.slice, svc)
   235  	err = fix.announcer.processSvcEvent(resource.Event[*slim_corev1.Service]{
   236  		Kind:   resource.Upsert,
   237  		Key:    resource.NewKey(svc),
   238  		Object: svc,
   239  		Done:   func(err error) {},
   240  	})
   241  	assert.NoError(t, err)
   242  
   243  	svcKey := serviceKey(blueService())
   244  	if !assert.Contains(t, fix.announcer.selectedServices, svcKey) {
   245  		return
   246  	}
   247  
   248  	rtx := fix.stateDB.ReadTxn()
   249  	iter := fix.proxyNeighborTable.All(rtx)
   250  	entries := statedb.Collect(iter)
   251  	assert.Len(t, entries, 0)
   252  
   253  	err = fix.announcer.processLeaderEvent(leaderElectionEvent{
   254  		typ:             leaderElectionLeading,
   255  		selectedService: fix.announcer.selectedServices[svcKey],
   256  	})
   257  	assert.NoError(t, err)
   258  
   259  	rtx = fix.stateDB.ReadTxn()
   260  	iter = fix.proxyNeighborTable.All(rtx)
   261  	entries = statedb.Collect(iter)
   262  	assert.Len(t, entries, 1)
   263  	assert.Equal(t, entries[0], &tables.L2AnnounceEntry{
   264  		L2AnnounceKey: tables.L2AnnounceKey{
   265  			IP:               netip.MustParseAddr(svc.Spec.ExternalIPs[0]),
   266  			NetworkInterface: policy.Spec.Interfaces[0],
   267  		},
   268  		Origins: []resource.Key{svcKey},
   269  	})
   270  
   271  	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
   272  	fix.announcer.params.JobGroup.Stop(ctx)
   273  	cancel()
   274  }
   275  
   276  // Test the happy path, but in every permutation of events. It should not matter in which order objects are processed
   277  // we should always end on the same result.
   278  func TestHappyPathPermutations(t *testing.T) {
   279  	addDevices := func(fix *fixture, tt *testing.T) {
   280  		fix.announcer.devices = []string{"eno01"}
   281  		err := fix.announcer.processDevicesChanged(context.Background())
   282  		assert.NoError(t, err)
   283  	}
   284  	addPolicy := func(fix *fixture, tt *testing.T) {
   285  		policy := bluePolicy()
   286  		fix.fakePolicyStore.slice = append(fix.fakePolicyStore.slice, policy)
   287  		err := fix.announcer.processPolicyEvent(context.Background(), resource.Event[*v2alpha1.CiliumL2AnnouncementPolicy]{
   288  			Kind:   resource.Upsert,
   289  			Key:    resource.NewKey(policy),
   290  			Object: policy,
   291  			Done:   func(err error) {},
   292  		})
   293  		assert.NoError(tt, err)
   294  	}
   295  	addService := func(fix *fixture, tt *testing.T) {
   296  		svc := blueService()
   297  		fix.fakeSvcStore.slice = append(fix.fakeSvcStore.slice, svc)
   298  		err := fix.announcer.processSvcEvent(resource.Event[*slim_corev1.Service]{
   299  			Kind:   resource.Upsert,
   300  			Key:    resource.NewKey(svc),
   301  			Object: svc,
   302  			Done:   func(err error) {},
   303  		})
   304  		assert.NoError(tt, err)
   305  	}
   306  
   307  	type fn struct {
   308  		name string
   309  		fn   func(fix *fixture, tt *testing.T)
   310  	}
   311  	funcs := []fn{
   312  		{name: "policy", fn: addPolicy},
   313  		{name: "svc", fn: addService},
   314  		{name: "dev", fn: addDevices},
   315  	}
   316  	run := func(fns []fn) {
   317  		var names []string
   318  		for _, fn := range fns {
   319  			names = append(names, fn.name)
   320  		}
   321  		t.Run(strings.Join(names, "_"), func(tt *testing.T) {
   322  			fix := newFixture(tt)
   323  			defer func() {
   324  				ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
   325  				fix.announcer.params.JobGroup.Stop(ctx)
   326  				cancel()
   327  			}()
   328  
   329  			err := fix.announcer.upsertLocalNode(context.Background(), blueNode())
   330  			assert.NoError(tt, err)
   331  
   332  			for _, fn := range fns {
   333  				fn.fn(fix, tt)
   334  			}
   335  
   336  			rtx := fix.stateDB.ReadTxn()
   337  			iter := fix.proxyNeighborTable.All(rtx)
   338  			entries := statedb.Collect(iter)
   339  			assert.Len(tt, entries, 0)
   340  
   341  			if assert.Contains(tt, fix.announcer.selectedServices, serviceKey(blueService())) {
   342  				err = fix.announcer.processLeaderEvent(leaderElectionEvent{
   343  					typ:             leaderElectionLeading,
   344  					selectedService: fix.announcer.selectedServices[serviceKey(blueService())],
   345  				})
   346  				assert.NoError(tt, err)
   347  			}
   348  
   349  			rtx = fix.stateDB.ReadTxn()
   350  			iter = fix.proxyNeighborTable.All(rtx)
   351  			entries = statedb.Collect(iter)
   352  			if assert.Len(tt, entries, 1) {
   353  				assert.Equal(tt, entries[0], &tables.L2AnnounceEntry{
   354  					L2AnnounceKey: tables.L2AnnounceKey{
   355  						IP:               netip.MustParseAddr(blueService().Spec.ExternalIPs[0]),
   356  						NetworkInterface: bluePolicy().Spec.Interfaces[0],
   357  					},
   358  					Origins: []resource.Key{serviceKey(blueService())},
   359  				})
   360  			}
   361  		})
   362  	}
   363  
   364  	// Heap's algorithm to run every permutation
   365  	// https://en.wikipedia.org/wiki/Heap%27s_algorithm#Details_of_the_algorithm
   366  	var generate func(k int, fns []fn)
   367  	generate = func(k int, fns []fn) {
   368  		if k == 1 {
   369  			run(fns)
   370  		} else {
   371  			generate(k-1, fns)
   372  
   373  			for i := 0; i < k-1; i++ {
   374  				if k%2 == 0 {
   375  					fns[i], fns[k-1] = fns[k-1], fns[i]
   376  				} else {
   377  					fns[0], fns[k-1] = fns[k-1], fns[0]
   378  				}
   379  
   380  				generate(k-1, fns)
   381  			}
   382  		}
   383  	}
   384  	generate(len(funcs), funcs)
   385  }
   386  
   387  // Test that when two policies select the same service, and one goes away, the service still stays selected
   388  func TestPolicyRedundancy(t *testing.T) {
   389  	fix := newFixture(t)
   390  
   391  	fix.announcer.devices = []string{"eno01"}
   392  	err := fix.announcer.processDevicesChanged(context.Background())
   393  	assert.NoError(t, err)
   394  
   395  	// Add local node
   396  	localNode := blueNode()
   397  	err = fix.announcer.upsertLocalNode(context.Background(), localNode)
   398  	assert.NoError(t, err)
   399  	assert.Equal(t, localNode, fix.announcer.localNode)
   400  
   401  	// Add first policy
   402  	policy := bluePolicy()
   403  	fix.fakePolicyStore.slice = append(fix.fakePolicyStore.slice, policy)
   404  	err = fix.announcer.processPolicyEvent(context.Background(), resource.Event[*v2alpha1.CiliumL2AnnouncementPolicy]{
   405  		Kind:   resource.Upsert,
   406  		Key:    resource.NewKey(policy),
   407  		Object: policy,
   408  		Done:   func(err error) {},
   409  	})
   410  	assert.NoError(t, err)
   411  
   412  	// Add second policy
   413  	policy2 := bluePolicy()
   414  	policy2.Name = "second-blue-policy"
   415  	fix.fakePolicyStore.slice = append(fix.fakePolicyStore.slice, policy2)
   416  	err = fix.announcer.processPolicyEvent(context.Background(), resource.Event[*v2alpha1.CiliumL2AnnouncementPolicy]{
   417  		Kind:   resource.Upsert,
   418  		Key:    resource.NewKey(policy2),
   419  		Object: policy2,
   420  		Done:   func(err error) {},
   421  	})
   422  	assert.NoError(t, err)
   423  
   424  	// Add service policy
   425  	svc := blueService()
   426  	fix.fakeSvcStore.slice = append(fix.fakeSvcStore.slice, svc)
   427  	err = fix.announcer.processSvcEvent(resource.Event[*slim_corev1.Service]{
   428  		Kind:   resource.Upsert,
   429  		Key:    resource.NewKey(svc),
   430  		Object: svc,
   431  		Done:   func(err error) {},
   432  	})
   433  	assert.NoError(t, err)
   434  
   435  	// Assert service is selected
   436  	svcKey := serviceKey(blueService())
   437  	if !assert.Contains(t, fix.announcer.selectedServices, svcKey) {
   438  		return
   439  	}
   440  
   441  	// Assert both policies selected service
   442  	assert.Contains(t, fix.announcer.selectedServices[svcKey].byPolicies, policyKey(policy))
   443  	assert.Contains(t, fix.announcer.selectedServices[svcKey].byPolicies, policyKey(policy2))
   444  
   445  	err = fix.announcer.processLeaderEvent(leaderElectionEvent{
   446  		typ:             leaderElectionLeading,
   447  		selectedService: fix.announcer.selectedServices[svcKey],
   448  	})
   449  	assert.NoError(t, err)
   450  
   451  	// Assert selected service turned into Proxy Neighbor Entry
   452  	rtx := fix.stateDB.ReadTxn()
   453  	iter := fix.proxyNeighborTable.All(rtx)
   454  	entries := statedb.Collect(iter)
   455  	assert.Len(t, entries, 1)
   456  	assert.Equal(t, entries[0], &tables.L2AnnounceEntry{
   457  		L2AnnounceKey: tables.L2AnnounceKey{
   458  			IP:               netip.MustParseAddr(svc.Spec.ExternalIPs[0]),
   459  			NetworkInterface: policy.Spec.Interfaces[0],
   460  		},
   461  		Origins: []resource.Key{svcKey},
   462  	})
   463  
   464  	// Delete second policy
   465  	idx := slices.Index(fix.fakePolicyStore.slice, policy2)
   466  	fix.fakePolicyStore.slice = slices.Delete(fix.fakePolicyStore.slice, idx, idx+1)
   467  	err = fix.announcer.processPolicyEvent(context.Background(), resource.Event[*v2alpha1.CiliumL2AnnouncementPolicy]{
   468  		Kind:   resource.Delete,
   469  		Key:    resource.NewKey(policy2),
   470  		Object: policy2,
   471  		Done:   func(err error) {},
   472  	})
   473  	assert.NoError(t, err)
   474  
   475  	// Assert only one policy selected
   476  	assert.Equal(t, []resource.Key{
   477  		policyKey(policy),
   478  	}, fix.announcer.selectedServices[svcKey].byPolicies)
   479  
   480  	// Assert Proxy Neighbor Entry still exists
   481  	rtx = fix.stateDB.ReadTxn()
   482  	iter = fix.proxyNeighborTable.All(rtx)
   483  	entries = statedb.Collect(iter)
   484  	assert.Len(t, entries, 1)
   485  	assert.Equal(t, entries[0], &tables.L2AnnounceEntry{
   486  		L2AnnounceKey: tables.L2AnnounceKey{
   487  			IP:               netip.MustParseAddr(svc.Spec.ExternalIPs[0]),
   488  			NetworkInterface: policy.Spec.Interfaces[0],
   489  		},
   490  		Origins: []resource.Key{svcKey},
   491  	})
   492  
   493  	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
   494  	fix.announcer.params.JobGroup.Stop(ctx)
   495  	cancel()
   496  }
   497  
   498  func baseUpdateSetup(t *testing.T) *fixture {
   499  	fix := newFixture(t)
   500  
   501  	fix.announcer.devices = []string{"eno01"}
   502  	err := fix.announcer.processDevicesChanged(context.Background())
   503  	require.NoError(t, err)
   504  	require.Len(t, fix.announcer.devices, 1)
   505  	require.Contains(t, fix.announcer.devices, "eno01")
   506  
   507  	localNode := blueNode()
   508  	err = fix.announcer.upsertLocalNode(context.Background(), localNode)
   509  	require.NoError(t, err)
   510  	require.Equal(t, localNode, fix.announcer.localNode)
   511  
   512  	policy := bluePolicy()
   513  	fix.fakePolicyStore.slice = append(fix.fakePolicyStore.slice, policy)
   514  	err = fix.announcer.processPolicyEvent(context.Background(), resource.Event[*v2alpha1.CiliumL2AnnouncementPolicy]{
   515  		Kind:   resource.Upsert,
   516  		Key:    resource.NewKey(policy),
   517  		Object: policy,
   518  		Done:   func(err error) {},
   519  	})
   520  	require.NoError(t, err)
   521  
   522  	require.Len(t, fix.announcer.selectedPolicies, 1)
   523  	require.Len(t, fix.announcer.selectedServices, 0)
   524  
   525  	svc := blueService()
   526  	fix.fakeSvcStore.slice = append(fix.fakeSvcStore.slice, svc)
   527  	err = fix.announcer.processSvcEvent(resource.Event[*slim_corev1.Service]{
   528  		Kind:   resource.Upsert,
   529  		Key:    resource.NewKey(svc),
   530  		Object: svc,
   531  		Done:   func(err error) {},
   532  	})
   533  	require.NoError(t, err)
   534  
   535  	require.Len(t, fix.announcer.selectedPolicies, 1)
   536  	require.Len(t, fix.announcer.selectedServices, 1)
   537  
   538  	err = fix.announcer.processLeaderEvent(leaderElectionEvent{
   539  		typ:             leaderElectionLeading,
   540  		selectedService: fix.announcer.selectedServices[serviceKey(svc)],
   541  	})
   542  	require.NoError(t, err)
   543  
   544  	rtx := fix.stateDB.ReadTxn()
   545  	iter := fix.proxyNeighborTable.All(rtx)
   546  	entries := statedb.Collect(iter)
   547  
   548  	require.Len(t, entries, 1)
   549  
   550  	return fix
   551  }
   552  
   553  // Update the host labels so the currently policy does not match anymore. Assert that policies are no longer selected
   554  // services are no longer selected and proxy neighbor entries are removed.
   555  func TestUpdateHostLabels_NoMatch(t *testing.T) {
   556  	fix := baseUpdateSetup(t)
   557  
   558  	node := blueNode()
   559  	node.Labels["color"] = "cyan"
   560  
   561  	err := fix.announcer.processLocalNodeEvent(context.Background(), resource.Event[*v2.CiliumNode]{
   562  		Kind:   resource.Upsert,
   563  		Key:    resource.NewKey(node),
   564  		Object: node,
   565  		Done:   func(err error) {},
   566  	})
   567  	assert.NoError(t, err)
   568  
   569  	assert.Len(t, fix.announcer.selectedPolicies, 0)
   570  	assert.Len(t, fix.announcer.selectedServices, 0)
   571  
   572  	// Assert Proxy Neighbor Entry is deleted
   573  	rtx := fix.stateDB.ReadTxn()
   574  	iter := fix.proxyNeighborTable.All(rtx)
   575  	entries := statedb.Collect(iter)
   576  	assert.Len(t, entries, 0)
   577  
   578  	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
   579  	fix.announcer.params.JobGroup.Stop(ctx)
   580  	cancel()
   581  }
   582  
   583  // When policies and services exist that currently don't match, assert that these are added properly when the labels
   584  // on the local node change.
   585  func TestUpdateHostLabels_AdditionalMatch(t *testing.T) {
   586  	fix := baseUpdateSetup(t)
   587  
   588  	// Check that active policies and selected services is 1
   589  	assert.Len(t, fix.announcer.selectedPolicies, 1)
   590  	assert.Len(t, fix.announcer.selectedServices, 1)
   591  
   592  	// Add a non matching policy
   593  	policy := bluePolicy()
   594  	policy.Name = "cyan-policy"
   595  	policy.Spec.NodeSelector.MatchLabels = map[string]string{
   596  		"hue": "cyan",
   597  	}
   598  	policy.Spec.ServiceSelector.MatchLabels = map[string]string{
   599  		"hue": "cyan",
   600  	}
   601  	fix.fakePolicyStore.slice = append(fix.fakePolicyStore.slice, policy)
   602  	err := fix.announcer.processPolicyEvent(context.Background(), resource.Event[*v2alpha1.CiliumL2AnnouncementPolicy]{
   603  		Kind:   resource.Upsert,
   604  		Key:    resource.NewKey(policy),
   605  		Object: policy,
   606  		Done:   func(err error) {},
   607  	})
   608  	assert.NoError(t, err)
   609  
   610  	// Add a non matching service
   611  	svc := blueService()
   612  	svc.Name = "cyan-service"
   613  	svc.Labels = map[string]string{
   614  		"hue": "cyan",
   615  	}
   616  	svc.Spec.ExternalIPs = []string{"192.168.2.2"}
   617  	fix.fakeSvcStore.slice = append(fix.fakeSvcStore.slice, svc)
   618  	err = fix.announcer.processSvcEvent(resource.Event[*slim_corev1.Service]{
   619  		Kind:   resource.Upsert,
   620  		Key:    resource.NewKey(svc),
   621  		Object: svc,
   622  		Done:   func(err error) {},
   623  	})
   624  	assert.NoError(t, err)
   625  
   626  	// Check that active policies and selected services is still 1
   627  	assert.Len(t, fix.announcer.selectedPolicies, 1)
   628  	assert.Len(t, fix.announcer.selectedServices, 1)
   629  
   630  	// Check that proxy neighbor entries are still 1
   631  	rtx := fix.stateDB.ReadTxn()
   632  	iter := fix.proxyNeighborTable.All(rtx)
   633  	entries := statedb.Collect(iter)
   634  	assert.Len(t, entries, 1)
   635  
   636  	node := blueNode()
   637  	node.Labels = map[string]string{
   638  		"color": "blue",
   639  		"hue":   "cyan",
   640  	}
   641  
   642  	err = fix.announcer.processLocalNodeEvent(context.Background(), resource.Event[*v2.CiliumNode]{
   643  		Kind:   resource.Upsert,
   644  		Key:    resource.NewKey(node),
   645  		Object: node,
   646  		Done:   func(err error) {},
   647  	})
   648  	assert.NoError(t, err)
   649  
   650  	// Check that active policies and selected services are now 2
   651  	assert.Len(t, fix.announcer.selectedPolicies, 2)
   652  	assert.Len(t, fix.announcer.selectedServices, 2)
   653  
   654  	// Become leader for service
   655  	err = fix.announcer.processLeaderEvent(leaderElectionEvent{
   656  		typ:             leaderElectionLeading,
   657  		selectedService: fix.announcer.selectedServices[serviceKey(svc)],
   658  	})
   659  	assert.NoError(t, err)
   660  
   661  	// Check that proxy neighbor entries are now 2
   662  	rtx = fix.stateDB.ReadTxn()
   663  	iter = fix.proxyNeighborTable.All(rtx)
   664  	entries = statedb.Collect(iter)
   665  	assert.Len(t, entries, 2)
   666  
   667  	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
   668  	fix.announcer.params.JobGroup.Stop(ctx)
   669  	cancel()
   670  }
   671  
   672  // Test that when a policy update causes a service to no longer match, that the service is removed
   673  func TestUpdatePolicy_NoMatch(t *testing.T) {
   674  	fix := baseUpdateSetup(t)
   675  
   676  	policy := bluePolicy()
   677  	policy.Spec.ServiceSelector.MatchLabels["color"] = "red"
   678  	fix.fakePolicyStore.slice[0] = policy
   679  	err := fix.announcer.processPolicyEvent(context.Background(), resource.Event[*v2alpha1.CiliumL2AnnouncementPolicy]{
   680  		Kind:   resource.Upsert,
   681  		Key:    resource.NewKey(policy),
   682  		Object: policy,
   683  		Done:   func(err error) {},
   684  	})
   685  	assert.NoError(t, err)
   686  
   687  	assert.Len(t, fix.announcer.selectedPolicies, 1)
   688  	assert.Len(t, fix.announcer.selectedServices, 0)
   689  
   690  	// Assert Proxy Neighbor Entry is deleted
   691  	rtx := fix.stateDB.ReadTxn()
   692  	iter := fix.proxyNeighborTable.All(rtx)
   693  	entries := statedb.Collect(iter)
   694  	assert.Len(t, entries, 0)
   695  
   696  	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
   697  	fix.announcer.params.JobGroup.Stop(ctx)
   698  	cancel()
   699  }
   700  
   701  // Test that when a policy is updated to match an addition service, that it is added and reflected in the proxy
   702  // neighbor table.
   703  func TestUpdatePolicy_AdditionalMatch(t *testing.T) {
   704  	fix := baseUpdateSetup(t)
   705  
   706  	// Add a non matching service
   707  	svc := blueService()
   708  	svc.Name = "cyan-service"
   709  	svc.Labels = map[string]string{
   710  		"color": "cyan",
   711  	}
   712  	svc.Spec.ExternalIPs = []string{"192.168.2.2"}
   713  	fix.fakeSvcStore.slice = append(fix.fakeSvcStore.slice, svc)
   714  	err := fix.announcer.processSvcEvent(resource.Event[*slim_corev1.Service]{
   715  		Kind:   resource.Upsert,
   716  		Key:    resource.NewKey(svc),
   717  		Object: svc,
   718  		Done:   func(err error) {},
   719  	})
   720  	assert.NoError(t, err)
   721  
   722  	policy := bluePolicy()
   723  	policy.Spec.ServiceSelector.MatchLabels = nil
   724  	policy.Spec.ServiceSelector.MatchExpressions = []slim_meta_v1.LabelSelectorRequirement{
   725  		{Key: "color", Operator: slim_meta_v1.LabelSelectorOpIn, Values: []string{"blue", "cyan"}},
   726  	}
   727  	fix.fakePolicyStore.slice[0] = policy
   728  	err = fix.announcer.processPolicyEvent(context.Background(), resource.Event[*v2alpha1.CiliumL2AnnouncementPolicy]{
   729  		Kind:   resource.Upsert,
   730  		Key:    resource.NewKey(policy),
   731  		Object: policy,
   732  		Done:   func(err error) {},
   733  	})
   734  	assert.NoError(t, err)
   735  
   736  	assert.Len(t, fix.announcer.selectedPolicies, 1)
   737  	assert.Len(t, fix.announcer.selectedServices, 2)
   738  
   739  	err = fix.announcer.processLeaderEvent(leaderElectionEvent{
   740  		typ:             leaderElectionLeading,
   741  		selectedService: fix.announcer.selectedServices[serviceKey(svc)],
   742  	})
   743  	assert.NoError(t, err)
   744  
   745  	// Assert that entries for both are added
   746  	rtx := fix.stateDB.ReadTxn()
   747  	iter := fix.proxyNeighborTable.All(rtx)
   748  	entries := statedb.Collect(iter)
   749  	assert.Len(t, entries, 2)
   750  
   751  	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
   752  	fix.announcer.params.JobGroup.Stop(ctx)
   753  	cancel()
   754  }
   755  
   756  // Test service selection under various conditions
   757  func TestPolicySelection(t *testing.T) {
   758  	fix := baseUpdateSetup(t)
   759  
   760  	// Setting external and LB IP to true should select a service from the baseUpdateSetup
   761  	policy := bluePolicy()
   762  	policy.Spec.ExternalIPs = true
   763  	policy.Spec.LoadBalancerIPs = true
   764  	fix.fakePolicyStore.slice[0] = policy
   765  	err := fix.announcer.processPolicyEvent(context.Background(), resource.Event[*v2alpha1.CiliumL2AnnouncementPolicy]{
   766  		Kind:   resource.Upsert,
   767  		Key:    resource.NewKey(policy),
   768  		Object: policy,
   769  		Done:   func(err error) {},
   770  	})
   771  	assert.NoError(t, err)
   772  
   773  	assert.Len(t, fix.announcer.selectedPolicies, 1)
   774  	assert.Len(t, fix.announcer.selectedServices, 1)
   775  
   776  	// A service with no externalIP and no LB IP should never be selected
   777  	svc := blueService()
   778  	svc.Spec.ExternalIPs = nil
   779  	svc.Status.LoadBalancer.Ingress = nil
   780  	fix.fakeSvcStore.slice[0] = svc
   781  	err = fix.announcer.processSvcEvent(resource.Event[*slim_corev1.Service]{
   782  		Kind:   resource.Upsert,
   783  		Key:    resource.NewKey(svc),
   784  		Object: svc,
   785  		Done:   func(err error) {},
   786  	})
   787  	assert.NoError(t, err)
   788  
   789  	assert.Len(t, fix.announcer.selectedPolicies, 1)
   790  	assert.Len(t, fix.announcer.selectedServices, 0)
   791  
   792  	// Setting external and LB IP to false should not select any services anymore
   793  	policy.Spec.ExternalIPs = false
   794  	policy.Spec.LoadBalancerIPs = false
   795  	fix.fakePolicyStore.slice[0] = policy
   796  	err = fix.announcer.processPolicyEvent(context.Background(), resource.Event[*v2alpha1.CiliumL2AnnouncementPolicy]{
   797  		Kind:   resource.Upsert,
   798  		Key:    resource.NewKey(policy),
   799  		Object: policy,
   800  		Done:   func(err error) {},
   801  	})
   802  	assert.NoError(t, err)
   803  
   804  	assert.Len(t, fix.announcer.selectedPolicies, 1)
   805  	assert.Len(t, fix.announcer.selectedServices, 0)
   806  
   807  	// Updating an existing non-selected service should not select it
   808  	svc.Spec = slim_corev1.ServiceSpec{
   809  		ExternalIPs: []string{"192.168.2.2"},
   810  	}
   811  	fix.fakeSvcStore.slice[0] = svc
   812  	err = fix.announcer.processSvcEvent(resource.Event[*slim_corev1.Service]{
   813  		Kind:   resource.Upsert,
   814  		Key:    resource.NewKey(svc),
   815  		Object: svc,
   816  		Done:   func(err error) {},
   817  	})
   818  	assert.NoError(t, err)
   819  
   820  	assert.Len(t, fix.announcer.selectedPolicies, 1)
   821  	assert.Len(t, fix.announcer.selectedServices, 0)
   822  
   823  	// Adding an LB IP to an existing non-selected service should not select it
   824  	svc.Status.LoadBalancer.Ingress = []slim_corev1.LoadBalancerIngress{
   825  		{IP: "192.168.2.7"},
   826  	}
   827  	fix.fakeSvcStore.slice[0] = svc
   828  	err = fix.announcer.processSvcEvent(resource.Event[*slim_corev1.Service]{
   829  		Kind:   resource.Upsert,
   830  		Key:    resource.NewKey(svc),
   831  		Object: svc,
   832  		Done:   func(err error) {},
   833  	})
   834  	assert.NoError(t, err)
   835  
   836  	assert.Len(t, fix.announcer.selectedPolicies, 1)
   837  	assert.Len(t, fix.announcer.selectedServices, 0)
   838  
   839  	// Altering the policy to select services with LB IPs should only have an entry for LB IPs
   840  	policy.Spec.ExternalIPs = false
   841  	policy.Spec.LoadBalancerIPs = true
   842  	fix.fakePolicyStore.slice[0] = policy
   843  	err = fix.announcer.processPolicyEvent(context.Background(), resource.Event[*v2alpha1.CiliumL2AnnouncementPolicy]{
   844  		Kind:   resource.Upsert,
   845  		Key:    resource.NewKey(policy),
   846  		Object: policy,
   847  		Done:   func(err error) {},
   848  	})
   849  	assert.NoError(t, err)
   850  	assert.Len(t, fix.announcer.selectedPolicies, 1)
   851  	assert.Len(t, fix.announcer.selectedServices, 1)
   852  
   853  	err = fix.announcer.processLeaderEvent(leaderElectionEvent{
   854  		typ:             leaderElectionLeading,
   855  		selectedService: fix.announcer.selectedServices[serviceKey(svc)],
   856  	})
   857  	assert.NoError(t, err)
   858  
   859  	rtx := fix.stateDB.ReadTxn()
   860  	iter := fix.proxyNeighborTable.All(rtx)
   861  	entries := statedb.Collect(iter)
   862  	assert.Len(t, entries, 1)
   863  	assert.Contains(t, entries, &tables.L2AnnounceEntry{
   864  		L2AnnounceKey: tables.L2AnnounceKey{
   865  			IP:               netip.MustParseAddr("192.168.2.7"),
   866  			NetworkInterface: bluePolicy().Spec.Interfaces[0],
   867  		},
   868  		Origins: []resource.Key{resource.NewKey(svc)},
   869  	})
   870  
   871  	// A service with an LB hostname but not an LB IP should not be selected
   872  	svc.Status.LoadBalancer.Ingress = []slim_corev1.LoadBalancerIngress{
   873  		{Hostname: "example.com"},
   874  	}
   875  	fix.fakeSvcStore.slice[0] = svc
   876  	err = fix.announcer.processSvcEvent(resource.Event[*slim_corev1.Service]{
   877  		Kind:   resource.Upsert,
   878  		Key:    resource.NewKey(svc),
   879  		Object: svc,
   880  		Done:   func(err error) {},
   881  	})
   882  	assert.NoError(t, err)
   883  
   884  	assert.Len(t, fix.announcer.selectedPolicies, 1)
   885  	assert.Len(t, fix.announcer.selectedServices, 0)
   886  
   887  }
   888  
   889  // Test that when the selected IP types in the policy changes, that proxy neighbor table is updated properly.
   890  func TestUpdatePolicy_ChangeIPType(t *testing.T) {
   891  	fix := baseUpdateSetup(t)
   892  
   893  	// Service has no LB IP so it should not be selected
   894  	policy := bluePolicy()
   895  	policy.Spec.ExternalIPs = false
   896  	policy.Spec.LoadBalancerIPs = true
   897  	fix.fakePolicyStore.slice[0] = policy
   898  	err := fix.announcer.processPolicyEvent(context.Background(), resource.Event[*v2alpha1.CiliumL2AnnouncementPolicy]{
   899  		Kind:   resource.Upsert,
   900  		Key:    resource.NewKey(policy),
   901  		Object: policy,
   902  		Done:   func(err error) {},
   903  	})
   904  	assert.NoError(t, err)
   905  
   906  	assert.Len(t, fix.announcer.selectedPolicies, 1)
   907  	assert.Len(t, fix.announcer.selectedServices, 0)
   908  
   909  	rtx := fix.stateDB.ReadTxn()
   910  	iter := fix.proxyNeighborTable.All(rtx)
   911  	entries := statedb.Collect(iter)
   912  	assert.Len(t, entries, 0)
   913  
   914  	// Adding an LB IP should select the service and create an entry
   915  	svc := blueService()
   916  	svc.Spec.ExternalIPs = nil
   917  	svc.Status.LoadBalancer.Ingress = []slim_corev1.LoadBalancerIngress{
   918  		{IP: "192.168.2.3"},
   919  	}
   920  	fix.fakeSvcStore.slice[0] = svc
   921  	err = fix.announcer.processSvcEvent(resource.Event[*slim_corev1.Service]{
   922  		Kind:   resource.Upsert,
   923  		Key:    resource.NewKey(svc),
   924  		Object: svc,
   925  		Done:   func(err error) {},
   926  	})
   927  	assert.NoError(t, err)
   928  
   929  	assert.Len(t, fix.announcer.selectedPolicies, 1)
   930  	assert.Len(t, fix.announcer.selectedServices, 1)
   931  
   932  	err = fix.announcer.processLeaderEvent(leaderElectionEvent{
   933  		typ:             leaderElectionLeading,
   934  		selectedService: fix.announcer.selectedServices[serviceKey(svc)],
   935  	})
   936  	assert.NoError(t, err)
   937  
   938  	rtx = fix.stateDB.ReadTxn()
   939  	iter = fix.proxyNeighborTable.All(rtx)
   940  	entries = statedb.Collect(iter)
   941  	assert.Len(t, entries, 1)
   942  	assert.Contains(t, entries, &tables.L2AnnounceEntry{
   943  		L2AnnounceKey: tables.L2AnnounceKey{
   944  			IP:               netip.MustParseAddr("192.168.2.3"),
   945  			NetworkInterface: bluePolicy().Spec.Interfaces[0],
   946  		},
   947  		Origins: []resource.Key{resource.NewKey(svc)},
   948  	})
   949  
   950  	// Setting an empty LB IP should unselect the service
   951  	svc.Status.LoadBalancer.Ingress = []slim_corev1.LoadBalancerIngress{
   952  		{IP: ""},
   953  	}
   954  	fix.fakeSvcStore.slice[0] = svc
   955  	err = fix.announcer.processSvcEvent(resource.Event[*slim_corev1.Service]{
   956  		Kind:   resource.Upsert,
   957  		Key:    resource.NewKey(svc),
   958  		Object: svc,
   959  		Done:   func(err error) {},
   960  	})
   961  	assert.NoError(t, err)
   962  
   963  	assert.Len(t, fix.announcer.selectedPolicies, 1)
   964  	assert.Len(t, fix.announcer.selectedServices, 0)
   965  
   966  	rtx = fix.stateDB.ReadTxn()
   967  	iter = fix.proxyNeighborTable.All(rtx)
   968  	entries = statedb.Collect(iter)
   969  	assert.Len(t, entries, 0)
   970  
   971  	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
   972  	fix.announcer.params.JobGroup.Stop(ctx)
   973  	cancel()
   974  }
   975  
   976  // Test that when the interfaces in a policy change, that the proxy neighbor entries are updated.
   977  func TestUpdatePolicy_ChangeInterfaces(t *testing.T) {
   978  	fix := baseUpdateSetup(t)
   979  
   980  	fix.announcer.devices = []string{"eno01", "eth0"}
   981  	err := fix.announcer.processDevicesChanged(context.Background())
   982  	assert.NoError(t, err)
   983  
   984  	policy := bluePolicy()
   985  	policy.Spec.Interfaces = []string{"eth0"}
   986  	fix.fakePolicyStore.slice[0] = policy
   987  	err = fix.announcer.processPolicyEvent(context.Background(), resource.Event[*v2alpha1.CiliumL2AnnouncementPolicy]{
   988  		Kind:   resource.Upsert,
   989  		Key:    resource.NewKey(policy),
   990  		Object: policy,
   991  		Done:   func(err error) {},
   992  	})
   993  	assert.NoError(t, err)
   994  
   995  	assert.Len(t, fix.announcer.selectedPolicies, 1)
   996  	assert.Len(t, fix.announcer.selectedServices, 1)
   997  
   998  	// Check that the old entry is deleted and the new entry added
   999  	rtx := fix.stateDB.ReadTxn()
  1000  	iter := fix.proxyNeighborTable.All(rtx)
  1001  	entries := statedb.Collect(iter)
  1002  	assert.Len(t, entries, 1)
  1003  	assert.Contains(t, entries, &tables.L2AnnounceEntry{
  1004  		L2AnnounceKey: tables.L2AnnounceKey{
  1005  			IP:               netip.MustParseAddr(blueService().Spec.ExternalIPs[0]),
  1006  			NetworkInterface: "eth0",
  1007  		},
  1008  		Origins: []resource.Key{resource.NewKey(blueService())},
  1009  	})
  1010  
  1011  	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
  1012  	fix.announcer.params.JobGroup.Stop(ctx)
  1013  	cancel()
  1014  }
  1015  
  1016  // Test that when a service deletes an IP the proxy neighbor table is updated accordingly
  1017  func TestUpdateService_DelIP(t *testing.T) {
  1018  	fix := baseUpdateSetup(t)
  1019  
  1020  	svc := blueService()
  1021  	svc.Spec.ExternalIPs = []string{}
  1022  	fix.fakeSvcStore.slice = append(fix.fakeSvcStore.slice, svc)
  1023  	err := fix.announcer.processSvcEvent(resource.Event[*slim_corev1.Service]{
  1024  		Kind:   resource.Upsert,
  1025  		Key:    resource.NewKey(svc),
  1026  		Object: svc,
  1027  		Done:   func(err error) {},
  1028  	})
  1029  	assert.NoError(t, err)
  1030  
  1031  	// Check that the entry for the IP was deleted
  1032  	rtx := fix.stateDB.ReadTxn()
  1033  	iter := fix.proxyNeighborTable.All(rtx)
  1034  	entries := statedb.Collect(iter)
  1035  	assert.Len(t, entries, 0)
  1036  
  1037  	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
  1038  	fix.announcer.params.JobGroup.Stop(ctx)
  1039  	cancel()
  1040  }
  1041  
  1042  // Test that when a service adds and IP, the proxy neighbor table is updated accordingly.
  1043  func TestUpdateService_AddIP(t *testing.T) {
  1044  	fix := baseUpdateSetup(t)
  1045  
  1046  	svc := blueService()
  1047  	svc.Spec.ExternalIPs = []string{"192.168.2.1", "192.168.2.2"}
  1048  	fix.fakeSvcStore.slice = append(fix.fakeSvcStore.slice, svc)
  1049  	err := fix.announcer.processSvcEvent(resource.Event[*slim_corev1.Service]{
  1050  		Kind:   resource.Upsert,
  1051  		Key:    resource.NewKey(svc),
  1052  		Object: svc,
  1053  		Done:   func(err error) {},
  1054  	})
  1055  	assert.NoError(t, err)
  1056  
  1057  	// Check that the interface on the proxy neighbor entry changed
  1058  	rtx := fix.stateDB.ReadTxn()
  1059  	iter := fix.proxyNeighborTable.All(rtx)
  1060  	entries := statedb.Collect(iter)
  1061  	assert.Len(t, entries, 2)
  1062  
  1063  	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
  1064  	fix.announcer.params.JobGroup.Stop(ctx)
  1065  	cancel()
  1066  }
  1067  
  1068  // Test that a service is removed if it no longer matches any policies
  1069  func TestUpdateService_NoMatch(t *testing.T) {
  1070  	fix := baseUpdateSetup(t)
  1071  
  1072  	svc := blueService()
  1073  	svc.Labels["color"] = "red"
  1074  	fix.fakeSvcStore.slice = append(fix.fakeSvcStore.slice, svc)
  1075  	err := fix.announcer.processSvcEvent(resource.Event[*slim_corev1.Service]{
  1076  		Kind:   resource.Upsert,
  1077  		Key:    resource.NewKey(svc),
  1078  		Object: svc,
  1079  		Done:   func(err error) {},
  1080  	})
  1081  	assert.NoError(t, err)
  1082  
  1083  	// Check that the entry got deleted
  1084  	rtx := fix.stateDB.ReadTxn()
  1085  	iter := fix.proxyNeighborTable.All(rtx)
  1086  	entries := statedb.Collect(iter)
  1087  	assert.Len(t, entries, 0)
  1088  
  1089  	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
  1090  	fix.announcer.params.JobGroup.Stop(ctx)
  1091  	cancel()
  1092  }
  1093  
  1094  // Test that when a service load balancer class is set to a supported value,
  1095  // it matches policies.
  1096  func TestUpdateService_LoadBalancerClassMatch(t *testing.T) {
  1097  	fix := baseUpdateSetup(t)
  1098  
  1099  	svc := blueService()
  1100  	svc.Spec.LoadBalancerClass = ptr.To[string](v2alpha1.L2AnnounceLoadBalancerClass)
  1101  	fix.fakeSvcStore.slice = append(fix.fakeSvcStore.slice, svc)
  1102  	err := fix.announcer.processSvcEvent(resource.Event[*slim_corev1.Service]{
  1103  		Kind:   resource.Upsert,
  1104  		Key:    resource.NewKey(svc),
  1105  		Object: svc,
  1106  		Done:   func(err error) {},
  1107  	})
  1108  	assert.NoError(t, err)
  1109  
  1110  	// Check that the entry got deleted
  1111  	rtx := fix.stateDB.ReadTxn()
  1112  	iter := fix.proxyNeighborTable.All(rtx)
  1113  	entries := statedb.Collect(iter)
  1114  	assert.Len(t, entries, 1)
  1115  
  1116  	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
  1117  	fix.announcer.params.JobGroup.Stop(ctx)
  1118  	cancel()
  1119  }
  1120  
  1121  // Test that when a service load balancer class is set to an unsupported value,
  1122  // it no longer matches any policies.
  1123  func TestUpdateService_LoadBalancerClassNotMatch(t *testing.T) {
  1124  	fix := baseUpdateSetup(t)
  1125  
  1126  	svc := blueService()
  1127  	svc.Spec.LoadBalancerClass = ptr.To[string]("unsupported.io/lb-class")
  1128  	fix.fakeSvcStore.slice = append(fix.fakeSvcStore.slice, svc)
  1129  	err := fix.announcer.processSvcEvent(resource.Event[*slim_corev1.Service]{
  1130  		Kind:   resource.Upsert,
  1131  		Key:    resource.NewKey(svc),
  1132  		Object: svc,
  1133  		Done:   func(err error) {},
  1134  	})
  1135  	assert.NoError(t, err)
  1136  
  1137  	// Check that the entry got deleted
  1138  	rtx := fix.stateDB.ReadTxn()
  1139  	iter := fix.proxyNeighborTable.All(rtx)
  1140  	entries := statedb.Collect(iter)
  1141  	assert.Len(t, entries, 0)
  1142  
  1143  	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
  1144  	fix.announcer.params.JobGroup.Stop(ctx)
  1145  	cancel()
  1146  }
  1147  
  1148  // Test that deleting a service removes its entries
  1149  func TestDelService(t *testing.T) {
  1150  	fix := baseUpdateSetup(t)
  1151  
  1152  	svc := blueService()
  1153  	fix.fakeSvcStore.slice = nil
  1154  	err := fix.announcer.processSvcEvent(resource.Event[*slim_corev1.Service]{
  1155  		Kind:   resource.Delete,
  1156  		Key:    resource.NewKey(svc),
  1157  		Object: svc,
  1158  		Done:   func(err error) {},
  1159  	})
  1160  	assert.NoError(t, err)
  1161  
  1162  	// Check that the entry got deleted
  1163  	rtx := fix.stateDB.ReadTxn()
  1164  	iter := fix.proxyNeighborTable.All(rtx)
  1165  	entries := statedb.Collect(iter)
  1166  	assert.Len(t, entries, 0)
  1167  
  1168  	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
  1169  	fix.announcer.params.JobGroup.Stop(ctx)
  1170  	cancel()
  1171  }
  1172  
  1173  // This tests affirms that the L2 announcer behaves as expected during it lifecycle, shutting down cleanly
  1174  func TestL2AnnouncerLifecycle(t *testing.T) {
  1175  	defer goleak.VerifyNone(t, goleak.IgnoreCurrent())
  1176  
  1177  	startCtx, cancel := context.WithTimeout(context.Background(), time.Minute)
  1178  	defer cancel()
  1179  
  1180  	h := hive.New(
  1181  		Cell,
  1182  		cell.Provide(tables.NewL2AnnounceTable),
  1183  		cell.Invoke(statedb.RegisterTable[*tables.L2AnnounceEntry]),
  1184  		cell.Provide(tables.NewDeviceTable, statedb.RWTable[*tables.Device].ToTable),
  1185  		cell.Invoke(statedb.RegisterTable[*tables.Device]),
  1186  		cell.Provide(func() *option.DaemonConfig {
  1187  			return &option.DaemonConfig{
  1188  				ConfigPatchMutex:      new(lock.RWMutex),
  1189  				EnableL2Announcements: true,
  1190  			}
  1191  		}),
  1192  		client.FakeClientCell,
  1193  		k8s.ResourcesCell,
  1194  		cell.Invoke(func(_ *L2Announcer) {}),
  1195  	)
  1196  	tlog := hivetest.Logger(t)
  1197  	err := h.Start(tlog, startCtx)
  1198  	if assert.NoError(t, err) {
  1199  		// Give everything some time to start
  1200  		time.Sleep(3 * time.Second)
  1201  
  1202  		stopCtx, cancel := context.WithTimeout(context.Background(), time.Minute)
  1203  		defer cancel()
  1204  
  1205  		err = h.Stop(tlog, stopCtx)
  1206  		assert.NoError(t, err)
  1207  	}
  1208  }