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 }