github.com/cilium/cilium@v1.16.2/pkg/bgpv1/manager/store/diffstore_test.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package store
     5  
     6  import (
     7  	"context"
     8  	"sync"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/cilium/hive/cell"
    13  	"github.com/cilium/hive/hivetest"
    14  	"k8s.io/apimachinery/pkg/watch"
    15  	k8sTesting "k8s.io/client-go/testing"
    16  
    17  	"github.com/cilium/cilium/pkg/bgpv1/agent/signaler"
    18  	"github.com/cilium/cilium/pkg/hive"
    19  	k8sClient "github.com/cilium/cilium/pkg/k8s/client"
    20  	"github.com/cilium/cilium/pkg/k8s/resource"
    21  	slimv1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/api/core/v1"
    22  	v1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/apis/meta/v1"
    23  	slim_fake "github.com/cilium/cilium/pkg/k8s/slim/k8s/client/clientset/versioned/fake"
    24  	"github.com/cilium/cilium/pkg/k8s/utils"
    25  )
    26  
    27  const (
    28  	testCallerID1 = "test1"
    29  	testCallerID2 = "test2"
    30  )
    31  
    32  type DiffStoreFixture struct {
    33  	diffStore DiffStore[*slimv1.Service]
    34  	signaler  *signaler.BGPCPSignaler
    35  	slimCs    *slim_fake.Clientset
    36  	hive      *hive.Hive
    37  	watching  chan struct{} // closed once we have ensured there is a service watcher registered
    38  }
    39  
    40  func newDiffStoreFixture() *DiffStoreFixture {
    41  	fixture := &DiffStoreFixture{
    42  		watching: make(chan struct{}),
    43  	}
    44  
    45  	// Create a new faked CRD client set with the pools as initial objects
    46  	fixture.slimCs = slim_fake.NewSimpleClientset()
    47  
    48  	var once sync.Once
    49  	fixture.slimCs.PrependWatchReactor("*", func(action k8sTesting.Action) (handled bool, ret watch.Interface, err error) {
    50  		w := action.(k8sTesting.WatchAction)
    51  		gvr := w.GetResource()
    52  		ns := w.GetNamespace()
    53  		watch, err := fixture.slimCs.Tracker().Watch(gvr, ns)
    54  		if err != nil {
    55  			return false, nil, err
    56  		}
    57  		once.Do(func() { close(fixture.watching) })
    58  		return true, watch, nil
    59  	})
    60  
    61  	// Construct a new Hive with faked out dependency cells.
    62  	fixture.hive = hive.New(
    63  		cell.Provide(func(lc cell.Lifecycle, c k8sClient.Clientset) resource.Resource[*slimv1.Service] {
    64  			return resource.New[*slimv1.Service](
    65  				lc, utils.ListerWatcherFromTyped[*slimv1.ServiceList](
    66  					c.Slim().CoreV1().Services(""),
    67  				),
    68  			)
    69  		}),
    70  
    71  		// Provide the faked client cells directly
    72  		cell.Provide(func() k8sClient.Clientset {
    73  			return &k8sClient.FakeClientset{
    74  				SlimFakeClientset: fixture.slimCs,
    75  			}
    76  		}),
    77  
    78  		cell.Module(
    79  			"bgpv1-test",
    80  			"Testing module for bgpv1",
    81  			cell.Provide(signaler.NewBGPCPSignaler),
    82  
    83  			cell.Invoke(func(
    84  				signaler *signaler.BGPCPSignaler,
    85  				diffFactory DiffStore[*slimv1.Service],
    86  			) {
    87  				fixture.signaler = signaler
    88  				fixture.diffStore = diffFactory
    89  			}),
    90  
    91  			cell.Provide(NewDiffStore[*slimv1.Service]),
    92  		),
    93  	)
    94  
    95  	return fixture
    96  }
    97  
    98  // Test that adding and deleting objects trigger signals
    99  func TestDiffSignal(t *testing.T) {
   100  	fixture := newDiffStoreFixture()
   101  	tracker := fixture.slimCs.Tracker()
   102  
   103  	tlog := hivetest.Logger(t)
   104  	err := fixture.hive.Start(tlog, context.Background())
   105  	if err != nil {
   106  		t.Fatal(err)
   107  	}
   108  	<-fixture.watching
   109  
   110  	fixture.diffStore.InitDiff(testCallerID1)
   111  	fixture.diffStore.InitDiff(testCallerID2)
   112  
   113  	// Add an initial object.
   114  	err = tracker.Add(&slimv1.Service{
   115  		ObjectMeta: v1.ObjectMeta{
   116  			Name: "service-a",
   117  		},
   118  	})
   119  	if err != nil {
   120  		t.Fatal(err)
   121  	}
   122  
   123  	timer := time.NewTimer(5 * time.Second)
   124  	select {
   125  	case <-fixture.signaler.Sig:
   126  		timer.Stop()
   127  	case <-timer.C:
   128  		t.Fatal("No signal sent by diffstore")
   129  	}
   130  
   131  	// 1 upsert for the caller 1
   132  	upserted, deleted, err := fixture.diffStore.Diff(testCallerID1)
   133  	if err != nil {
   134  		t.Fatal(err)
   135  	}
   136  	if len(upserted) != 1 {
   137  		t.Fatal("Initial upserted not one")
   138  	}
   139  	if len(deleted) != 0 {
   140  		t.Fatal("Initial deleted not zero")
   141  	}
   142  
   143  	// Add an object after init
   144  
   145  	err = tracker.Add(&slimv1.Service{
   146  		ObjectMeta: v1.ObjectMeta{
   147  			Name: "service-b",
   148  		},
   149  	})
   150  	if err != nil {
   151  		t.Fatal(err)
   152  	}
   153  
   154  	timer = time.NewTimer(5 * time.Second)
   155  	select {
   156  	case <-fixture.signaler.Sig:
   157  		timer.Stop()
   158  	case <-timer.C:
   159  		t.Fatal("No signal sent by diffstore")
   160  	}
   161  
   162  	// 1 upsert for the caller 1
   163  	upserted, deleted, err = fixture.diffStore.Diff(testCallerID1)
   164  	if err != nil {
   165  		t.Fatal(err)
   166  	}
   167  	if len(upserted) != 1 {
   168  		t.Fatal("Runtime upserted not one")
   169  	}
   170  	if len(deleted) != 0 {
   171  		t.Fatal("Runtime deleted not zero")
   172  	}
   173  
   174  	// 2 upserts for the caller 2
   175  	upserted, deleted, err = fixture.diffStore.Diff(testCallerID2)
   176  	if err != nil {
   177  		t.Fatal(err)
   178  	}
   179  	if len(upserted) != 2 {
   180  		t.Fatal("Runtime upserted not two")
   181  	}
   182  	if len(deleted) != 0 {
   183  		t.Fatal("Runtime deleted not zero")
   184  	}
   185  
   186  	// Delete an object after init
   187  
   188  	err = tracker.Delete(slimv1.SchemeGroupVersion.WithResource("services"), "", "service-b")
   189  	if err != nil {
   190  		t.Fatal(err)
   191  	}
   192  
   193  	timer = time.NewTimer(5 * time.Second)
   194  	select {
   195  	case <-fixture.signaler.Sig:
   196  		timer.Stop()
   197  	case <-timer.C:
   198  		t.Fatal("No signal sent by diffstore")
   199  	}
   200  
   201  	// 1 deleted for the caller 1
   202  	upserted, deleted, err = fixture.diffStore.Diff(testCallerID1)
   203  	if err != nil {
   204  		t.Fatal(err)
   205  	}
   206  	if len(upserted) != 0 {
   207  		t.Fatal("Runtime upserted not zero")
   208  	}
   209  	if len(deleted) != 1 {
   210  		t.Fatal("Runtime deleted not one")
   211  	}
   212  
   213  	// 1 deleted for the caller 2
   214  	upserted, deleted, err = fixture.diffStore.Diff(testCallerID2)
   215  	if err != nil {
   216  		t.Fatal(err)
   217  	}
   218  	if len(upserted) != 0 {
   219  		t.Fatal("Runtime upserted not zero")
   220  	}
   221  	if len(deleted) != 1 {
   222  		t.Fatal("Runtime deleted not one")
   223  	}
   224  
   225  	err = fixture.hive.Stop(tlog, context.Background())
   226  	if err != nil {
   227  		t.Fatal(err)
   228  	}
   229  }
   230  
   231  // Test that multiple events are correctly combined.
   232  func TestDiffUpsertCoalesce(t *testing.T) {
   233  	fixture := newDiffStoreFixture()
   234  	tracker := fixture.slimCs.Tracker()
   235  
   236  	tlog := hivetest.Logger(t)
   237  	err := fixture.hive.Start(tlog, context.Background())
   238  	if err != nil {
   239  		t.Fatal(err)
   240  	}
   241  	<-fixture.watching
   242  
   243  	fixture.diffStore.InitDiff(testCallerID1)
   244  
   245  	// Add first object
   246  	err = tracker.Add(&slimv1.Service{
   247  		ObjectMeta: v1.ObjectMeta{
   248  			Name: "service-a",
   249  		},
   250  	})
   251  	if err != nil {
   252  		t.Fatal(err)
   253  	}
   254  
   255  	// Add second object
   256  	err = tracker.Add(&slimv1.Service{
   257  		ObjectMeta: v1.ObjectMeta{
   258  			Name: "service-b",
   259  		},
   260  	})
   261  	if err != nil {
   262  		t.Fatal(err)
   263  	}
   264  
   265  	// Wait a second for changes to be processed
   266  	time.Sleep(time.Second)
   267  
   268  	// Check that we have a signal
   269  	timer := time.NewTimer(5 * time.Second)
   270  	select {
   271  	case <-fixture.signaler.Sig:
   272  		timer.Stop()
   273  	case <-timer.C:
   274  		t.Fatal("No signal sent by diffstore")
   275  	}
   276  
   277  	upserted, deleted, err := fixture.diffStore.Diff(testCallerID1)
   278  	if err != nil {
   279  		t.Fatal(err)
   280  	}
   281  
   282  	if len(upserted) != 2 {
   283  		t.Fatal("Expected 2 upserted objects")
   284  	}
   285  
   286  	if len(deleted) != 0 {
   287  		t.Fatal("Expected 0 deleted objects")
   288  	}
   289  
   290  	// Update first object
   291  	err = tracker.Update(
   292  		slimv1.SchemeGroupVersion.WithResource("services"),
   293  		&slimv1.Service{
   294  			ObjectMeta: v1.ObjectMeta{
   295  				Name: "service-a",
   296  			},
   297  			Spec: slimv1.ServiceSpec{
   298  				ClusterIP: "1.2.3.4",
   299  			},
   300  		},
   301  		"",
   302  	)
   303  	if err != nil {
   304  		t.Fatal(err)
   305  	}
   306  
   307  	err = tracker.Delete(slimv1.SchemeGroupVersion.WithResource("services"), "", "service-b")
   308  	if err != nil {
   309  		t.Fatal(err)
   310  	}
   311  
   312  	// Wait a second for changes to be processed
   313  	time.Sleep(time.Second)
   314  
   315  	// Check that we have a signal
   316  	timer = time.NewTimer(5 * time.Second)
   317  	select {
   318  	case <-fixture.signaler.Sig:
   319  		timer.Stop()
   320  	case <-timer.C:
   321  		t.Fatal("No signal sent by diffstore")
   322  	}
   323  
   324  	upserted, deleted, err = fixture.diffStore.Diff(testCallerID1)
   325  	if err != nil {
   326  		t.Fatal(err)
   327  	}
   328  
   329  	if len(upserted) != 1 {
   330  		t.Fatal("Expected 1 upserted object")
   331  	}
   332  
   333  	if len(deleted) != 1 {
   334  		t.Fatal("Expected 1 deleted object")
   335  	}
   336  
   337  	// Update first object once
   338  	err = tracker.Update(
   339  		slimv1.SchemeGroupVersion.WithResource("services"),
   340  		&slimv1.Service{
   341  			ObjectMeta: v1.ObjectMeta{
   342  				Name: "service-a",
   343  			},
   344  			Spec: slimv1.ServiceSpec{
   345  				ClusterIP: "2.3.4.5",
   346  			},
   347  		},
   348  		"",
   349  	)
   350  	if err != nil {
   351  		t.Fatal(err)
   352  	}
   353  
   354  	// Update first object twice
   355  	err = tracker.Update(
   356  		slimv1.SchemeGroupVersion.WithResource("services"),
   357  		&slimv1.Service{
   358  			ObjectMeta: v1.ObjectMeta{
   359  				Name: "service-a",
   360  			},
   361  			Spec: slimv1.ServiceSpec{
   362  				ClusterIP: "3.4.5.6",
   363  			},
   364  		},
   365  		"",
   366  	)
   367  	if err != nil {
   368  		t.Fatal(err)
   369  	}
   370  
   371  	// Wait a second for changes to be processed
   372  	time.Sleep(time.Second)
   373  
   374  	// Check that we have a signal
   375  	timer = time.NewTimer(5 * time.Second)
   376  	select {
   377  	case <-fixture.signaler.Sig:
   378  		timer.Stop()
   379  	case <-timer.C:
   380  		t.Fatal("No signal sent by diffstore")
   381  	}
   382  
   383  	upserted, deleted, err = fixture.diffStore.Diff(testCallerID1)
   384  	if err != nil {
   385  		t.Fatal(err)
   386  	}
   387  
   388  	if len(upserted) != 1 {
   389  		t.Fatal("Expected 1 upserted object")
   390  	}
   391  
   392  	if len(deleted) != 0 {
   393  		t.Fatal("Expected 1 deleted object")
   394  	}
   395  
   396  	if upserted[0].Spec.ClusterIP != "3.4.5.6" {
   397  		t.Fatal("Expected to only see the latest update")
   398  	}
   399  
   400  	err = fixture.hive.Stop(tlog, context.Background())
   401  	if err != nil {
   402  		t.Fatal(err)
   403  	}
   404  }