github.com/cilium/cilium@v1.16.2/pkg/k8s/service_cache_test.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 package k8s 5 6 import ( 7 "context" 8 "fmt" 9 "net" 10 "testing" 11 "time" 12 13 "github.com/cilium/hive/hivetest" 14 "github.com/cilium/statedb" 15 "github.com/cilium/stream" 16 "github.com/stretchr/testify/require" 17 v1 "k8s.io/api/core/v1" 18 19 cmtypes "github.com/cilium/cilium/pkg/clustermesh/types" 20 datapathTables "github.com/cilium/cilium/pkg/datapath/tables" 21 slim_corev1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/api/core/v1" 22 slim_discovery_v1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/api/discovery/v1" 23 slim_metav1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/apis/meta/v1" 24 "github.com/cilium/cilium/pkg/loadbalancer" 25 "github.com/cilium/cilium/pkg/lock" 26 "github.com/cilium/cilium/pkg/node" 27 "github.com/cilium/cilium/pkg/node/types" 28 "github.com/cilium/cilium/pkg/option" 29 serviceStore "github.com/cilium/cilium/pkg/service/store" 30 "github.com/cilium/cilium/pkg/testutils" 31 ) 32 33 func newDB(t *testing.T) (*statedb.DB, statedb.RWTable[datapathTables.NodeAddress]) { 34 db := statedb.New() 35 nodeAddrs, err := datapathTables.NewNodeAddressTable() 36 require.NoError(t, err) 37 38 err = db.RegisterTable(nodeAddrs) 39 require.NoError(t, err) 40 41 txn := db.WriteTxn(nodeAddrs) 42 for _, addr := range datapathTables.TestAddresses { 43 nodeAddrs.Insert(txn, addr) 44 } 45 txn.Commit() 46 47 return db, nodeAddrs 48 } 49 50 func TestGetUniqueServiceFrontends(t *testing.T) { 51 svcID1 := ServiceID{Name: "svc1", Namespace: "default"} 52 svcID2 := ServiceID{Name: "svc2", Namespace: "default"} 53 54 endpoints := Endpoints{ 55 Backends: map[cmtypes.AddrCluster]*Backend{ 56 cmtypes.MustParseAddrCluster("3.3.3.3"): { 57 Ports: map[string]*loadbalancer.L4Addr{ 58 "port": { 59 Protocol: loadbalancer.TCP, 60 Port: 80, 61 }, 62 }, 63 }, 64 }, 65 } 66 67 db, nodeAddrs := newDB(t) 68 cache := NewServiceCache(db, nodeAddrs) 69 70 cache.services = map[ServiceID]*Service{ 71 svcID1: { 72 FrontendIPs: []net.IP{net.ParseIP("1.1.1.1")}, 73 Ports: map[loadbalancer.FEPortName]*loadbalancer.L4Addr{ 74 loadbalancer.FEPortName("foo"): { 75 Protocol: loadbalancer.TCP, 76 Port: 10, 77 }, 78 loadbalancer.FEPortName("bar"): { 79 Protocol: loadbalancer.TCP, 80 Port: 20, 81 }, 82 }, 83 }, 84 svcID2: { 85 FrontendIPs: []net.IP{net.ParseIP("2.2.2.2")}, 86 Ports: map[loadbalancer.FEPortName]*loadbalancer.L4Addr{ 87 loadbalancer.FEPortName("bar"): { 88 Protocol: loadbalancer.UDP, 89 Port: 20, 90 }, 91 }, 92 }, 93 } 94 cache.endpoints = map[ServiceID]*EndpointSlices{ 95 svcID1: { 96 epSlices: map[string]*Endpoints{ 97 "": &endpoints, 98 }, 99 }, 100 svcID2: { 101 epSlices: map[string]*Endpoints{ 102 "": &endpoints, 103 }, 104 }, 105 } 106 107 frontends := cache.UniqueServiceFrontends() 108 require.EqualValues(t, FrontendList{ 109 "1.1.1.1:10/TCP": {}, 110 "1.1.1.1:20/TCP": {}, 111 "2.2.2.2:20/UDP": {}, 112 }, frontends) 113 114 scopes := []uint8{loadbalancer.ScopeExternal, loadbalancer.ScopeInternal} 115 for _, scope := range scopes { 116 // Validate all frontends as exact matches 117 // These should match only for external scope 118 exact_match_ok := scope == loadbalancer.ScopeExternal 119 addrCluster1 := cmtypes.MustParseAddrCluster("1.1.1.1") 120 addrCluster2 := cmtypes.MustParseAddrCluster("2.2.2.2") 121 frontend := loadbalancer.NewL3n4Addr(loadbalancer.TCP, addrCluster1, 10, scope) 122 require.Equal(t, exact_match_ok, frontends.LooseMatch(*frontend)) 123 frontend = loadbalancer.NewL3n4Addr(loadbalancer.TCP, addrCluster1, 20, scope) 124 require.Equal(t, exact_match_ok, frontends.LooseMatch(*frontend)) 125 frontend = loadbalancer.NewL3n4Addr(loadbalancer.UDP, addrCluster2, 20, scope) 126 require.Equal(t, exact_match_ok, frontends.LooseMatch(*frontend)) 127 128 // Validate protocol mismatch on exact match 129 frontend = loadbalancer.NewL3n4Addr(loadbalancer.TCP, addrCluster2, 20, scope) 130 require.Equal(t, false, frontends.LooseMatch(*frontend)) 131 132 // Validate protocol wildcard matching 133 // These should match only for external scope 134 wild_match_ok := scope == loadbalancer.ScopeExternal 135 frontend = loadbalancer.NewL3n4Addr(loadbalancer.NONE, addrCluster2, 20, scope) 136 require.Equal(t, wild_match_ok, frontends.LooseMatch(*frontend)) 137 frontend = loadbalancer.NewL3n4Addr(loadbalancer.NONE, addrCluster1, 10, scope) 138 require.Equal(t, wild_match_ok, frontends.LooseMatch(*frontend)) 139 frontend = loadbalancer.NewL3n4Addr(loadbalancer.NONE, addrCluster1, 20, scope) 140 require.Equal(t, wild_match_ok, frontends.LooseMatch(*frontend)) 141 } 142 } 143 144 func TestServiceCacheEndpoints(t *testing.T) { 145 endpoints := ParseEndpoints(&slim_corev1.Endpoints{ 146 ObjectMeta: slim_metav1.ObjectMeta{ 147 Name: "foo", 148 Namespace: "bar", 149 }, 150 Subsets: []slim_corev1.EndpointSubset{ 151 { 152 Addresses: []slim_corev1.EndpointAddress{{IP: "2.2.2.2"}}, 153 Ports: []slim_corev1.EndpointPort{ 154 { 155 Name: "http-test-svc", 156 Port: 8080, 157 Protocol: slim_corev1.ProtocolTCP, 158 }, 159 }, 160 }, 161 }, 162 }) 163 164 updateEndpoints := func(svcCache *ServiceCache, swgEps *lock.StoppableWaitGroup) { 165 svcCache.UpdateEndpoints(endpoints, swgEps) 166 } 167 deleteEndpoints := func(svcCache *ServiceCache, swgEps *lock.StoppableWaitGroup) { 168 svcCache.DeleteEndpoints(endpoints.EndpointSliceID, swgEps) 169 } 170 171 testServiceCache(t, updateEndpoints, deleteEndpoints) 172 } 173 174 func TestServiceCacheEndpointSlice(t *testing.T) { 175 endpoints := ParseEndpointSliceV1(&slim_discovery_v1.EndpointSlice{ 176 AddressType: slim_discovery_v1.AddressTypeIPv4, 177 ObjectMeta: slim_metav1.ObjectMeta{ 178 Name: "foo-afbh9", 179 Namespace: "bar", 180 Labels: map[string]string{ 181 slim_discovery_v1.LabelServiceName: "foo", 182 }, 183 }, 184 Endpoints: []slim_discovery_v1.Endpoint{ 185 { 186 Addresses: []string{ 187 "2.2.2.2", 188 }, 189 }, 190 }, 191 Ports: []slim_discovery_v1.EndpointPort{ 192 { 193 Name: func() *string { a := "http-test-svc"; return &a }(), 194 Protocol: func() *slim_corev1.Protocol { a := slim_corev1.ProtocolTCP; return &a }(), 195 Port: func() *int32 { a := int32(8080); return &a }(), 196 }, 197 }, 198 }) 199 200 updateEndpoints := func(svcCache *ServiceCache, swgEps *lock.StoppableWaitGroup) { 201 svcCache.UpdateEndpoints(endpoints, swgEps) 202 } 203 deleteEndpoints := func(svcCache *ServiceCache, swgEps *lock.StoppableWaitGroup) { 204 svcCache.DeleteEndpoints(endpoints.EndpointSliceID, swgEps) 205 } 206 207 testServiceCache(t, updateEndpoints, deleteEndpoints) 208 } 209 210 func testServiceCache(t *testing.T, 211 updateEndpointsCB, deleteEndpointsCB func(svcCache *ServiceCache, swgEps *lock.StoppableWaitGroup)) { 212 213 db, nodeAddrs := newDB(t) 214 svcCache := NewServiceCache(db, nodeAddrs) 215 216 k8sSvc := &slim_corev1.Service{ 217 ObjectMeta: slim_metav1.ObjectMeta{ 218 Name: "foo", 219 Namespace: "bar", 220 Labels: map[string]string{ 221 "foo": "bar", 222 }, 223 }, 224 Spec: slim_corev1.ServiceSpec{ 225 ClusterIP: "127.0.0.1", 226 Selector: map[string]string{ 227 "foo": "bar", 228 }, 229 Type: slim_corev1.ServiceTypeClusterIP, 230 }, 231 } 232 233 swgSvcs := lock.NewStoppableWaitGroup() 234 svcID := svcCache.UpdateService(k8sSvc, swgSvcs) 235 236 time.Sleep(100 * time.Millisecond) 237 238 select { 239 case <-svcCache.Events: 240 t.Error("Unexpected service event received before endpoints have been imported") 241 default: 242 } 243 244 swgEps := lock.NewStoppableWaitGroup() 245 updateEndpointsCB(svcCache, swgEps) 246 247 // The service should be ready as both service and endpoints have been 248 // imported 249 require.Nil(t, testutils.WaitUntil(func() bool { 250 event := <-svcCache.Events 251 defer event.SWG.Done() 252 require.Equal(t, UpdateService, event.Action) 253 require.Equal(t, svcID, event.ID) 254 return true 255 }, 2*time.Second)) 256 257 endpoints, ready := svcCache.correlateEndpoints(svcID) 258 require.Equal(t, true, ready) 259 require.Equal(t, "2.2.2.2:8080/TCP", endpoints.String()) 260 261 // Updating the service without chaning it should not result in an event 262 svcCache.UpdateService(k8sSvc, swgSvcs) 263 time.Sleep(100 * time.Millisecond) 264 select { 265 case <-svcCache.Events: 266 t.Error("Unexpected service event received for unchanged service object") 267 default: 268 } 269 270 // Add late subscriber, it should receive all events until it unsubscribes 271 subCtx, subCancel := context.WithCancel(context.Background()) 272 svcNotifications := stream.ToChannel(subCtx, svcCache.Notifications(), stream.WithBufferSize(1)) 273 274 // Deleting the service will result in a service delete event 275 svcCache.DeleteService(k8sSvc, swgSvcs) 276 require.Nil(t, testutils.WaitUntil(func() bool { 277 event := <-svcCache.Events 278 defer event.SWG.Done() 279 require.Equal(t, DeleteService, event.Action) 280 require.Equal(t, svcID, event.ID) 281 282 n := <-svcNotifications 283 require.Equal(t, DeleteService, n.Action) 284 require.Equal(t, svcID, n.ID) 285 286 return true 287 }, 2*time.Second)) 288 289 // Reinserting the service should re-match with the still existing endpoints 290 svcCache.UpdateService(k8sSvc, swgSvcs) 291 require.Nil(t, testutils.WaitUntil(func() bool { 292 event := <-svcCache.Events 293 defer event.SWG.Done() 294 require.Equal(t, UpdateService, event.Action) 295 require.Equal(t, svcID, event.ID) 296 297 n := <-svcNotifications 298 require.Equal(t, UpdateService, n.Action) 299 require.Equal(t, svcID, n.ID) 300 301 return true 302 }, 2*time.Second)) 303 304 // Deleting the endpoints will result in a service update event 305 deleteEndpointsCB(svcCache, swgEps) 306 require.Nil(t, testutils.WaitUntil(func() bool { 307 event := <-svcCache.Events 308 defer event.SWG.Done() 309 require.Equal(t, UpdateService, event.Action) 310 require.Equal(t, svcID, event.ID) 311 312 n := <-svcNotifications 313 require.Equal(t, UpdateService, n.Action) 314 require.Equal(t, svcID, n.ID) 315 316 return true 317 }, 2*time.Second)) 318 319 // Stop subscription and wait for it to expire 320 subCancel() 321 require.Nil(t, testutils.WaitUntil(func() bool { 322 _, stillSubscribed := <-svcNotifications 323 return !stillSubscribed 324 }, 2*time.Second)) 325 326 endpoints, serviceReady := svcCache.correlateEndpoints(svcID) 327 require.Equal(t, false, serviceReady) 328 require.Equal(t, "", endpoints.String()) 329 330 // Reinserting the endpoints should re-match with the still existing service 331 updateEndpointsCB(svcCache, swgEps) 332 require.Nil(t, testutils.WaitUntil(func() bool { 333 event := <-svcCache.Events 334 defer event.SWG.Done() 335 require.Equal(t, UpdateService, event.Action) 336 require.Equal(t, svcID, event.ID) 337 return true 338 }, 2*time.Second)) 339 340 endpoints, serviceReady = svcCache.correlateEndpoints(svcID) 341 require.Equal(t, true, serviceReady) 342 require.Equal(t, "2.2.2.2:8080/TCP", endpoints.String()) 343 344 // Deleting the service will result in a service delete event 345 svcCache.DeleteService(k8sSvc, swgSvcs) 346 require.Nil(t, testutils.WaitUntil(func() bool { 347 event := <-svcCache.Events 348 defer event.SWG.Done() 349 require.Equal(t, DeleteService, event.Action) 350 require.Equal(t, svcID, event.ID) 351 return true 352 }, 2*time.Second)) 353 354 // Deleting the endpoints will not emit an event as the notification 355 // was sent out when the service was deleted. 356 deleteEndpointsCB(svcCache, swgEps) 357 time.Sleep(100 * time.Millisecond) 358 select { 359 case <-svcCache.Events: 360 t.Error("Unexpected service delete event received") 361 default: 362 } 363 364 swgSvcs.Stop() 365 require.Nil(t, testutils.WaitUntil(func() bool { 366 swgSvcs.Wait() 367 return true 368 }, 2*time.Second)) 369 370 swgEps.Stop() 371 require.Nil(t, testutils.WaitUntil(func() bool { 372 swgEps.Wait() 373 return true 374 }, 2*time.Second)) 375 } 376 377 func TestForEachService(t *testing.T) { 378 db, nodeAddrs := newDB(t) 379 svcCache := NewServiceCache(db, nodeAddrs) 380 381 k8sSvc1 := &slim_corev1.Service{ 382 ObjectMeta: slim_metav1.ObjectMeta{ 383 Name: "foo", 384 Namespace: "bar", 385 Labels: map[string]string{ 386 "foo": "bar", 387 }, 388 }, 389 Spec: slim_corev1.ServiceSpec{ 390 ClusterIP: "127.0.0.1", 391 Selector: map[string]string{ 392 "foo": "bar", 393 }, 394 Type: slim_corev1.ServiceTypeClusterIP, 395 }, 396 } 397 k8sEndpoints1 := ParseEndpoints(&slim_corev1.Endpoints{ 398 ObjectMeta: slim_metav1.ObjectMeta{ 399 Name: "foo", 400 Namespace: "bar", 401 }, 402 Subsets: []slim_corev1.EndpointSubset{ 403 { 404 Addresses: []slim_corev1.EndpointAddress{{IP: "2.2.2.2"}}, 405 Ports: []slim_corev1.EndpointPort{ 406 { 407 Name: "http-test-svc", 408 Port: 8080, 409 Protocol: slim_corev1.ProtocolTCP, 410 }, 411 }, 412 }, 413 }, 414 }) 415 416 k8sSvc2 := &slim_corev1.Service{ 417 ObjectMeta: slim_metav1.ObjectMeta{ 418 Name: "baz", 419 Namespace: "qux", 420 }, 421 Spec: slim_corev1.ServiceSpec{ 422 ClusterIP: "192.168.1.1", 423 Type: slim_corev1.ServiceTypeClusterIP, 424 }, 425 } 426 k8sEndpoints2 := ParseEndpointSliceV1(&slim_discovery_v1.EndpointSlice{ 427 AddressType: slim_discovery_v1.AddressTypeIPv4, 428 ObjectMeta: slim_metav1.ObjectMeta{ 429 Name: "baz-xxxxx", 430 Namespace: "qux", 431 Labels: map[string]string{ 432 slim_discovery_v1.LabelServiceName: "baz", 433 }, 434 }, 435 Endpoints: []slim_discovery_v1.Endpoint{ 436 { 437 Addresses: []string{ 438 "1.1.1.1", 439 "1.0.0.1", 440 }, 441 }, 442 }, 443 }) 444 445 swg := lock.NewStoppableWaitGroup() 446 svcCache.UpdateService(k8sSvc1, swg) 447 svcCache.UpdateService(k8sSvc2, swg) 448 449 svcID1, eps1 := svcCache.UpdateEndpoints(k8sEndpoints1, swg) 450 require.Nil(t, testutils.WaitUntil(func() bool { 451 event := <-svcCache.Events 452 defer event.SWG.Done() 453 require.Equal(t, UpdateService, event.Action) 454 require.Equal(t, svcID1, event.ID) 455 require.Equal(t, eps1, event.Endpoints) 456 return true 457 }, 2*time.Second)) 458 459 svcID2, eps2 := svcCache.UpdateEndpoints(k8sEndpoints2, swg) 460 require.Nil(t, testutils.WaitUntil(func() bool { 461 println("waiting for events2") 462 event := <-svcCache.Events 463 defer event.SWG.Done() 464 require.Equal(t, UpdateService, event.Action) 465 require.Equal(t, svcID2, event.ID) 466 require.Equal(t, eps2, event.Endpoints) 467 return true 468 }, 2*time.Second)) 469 470 services := map[ServiceID]*Endpoints{} 471 svcCache.ForEachService(func(svcID ServiceID, svc *Service, eps *Endpoints) bool { 472 services[svcID] = eps 473 return true 474 }) 475 require.Equal(t, map[ServiceID]*Endpoints{ 476 svcID1: eps1, 477 svcID2: eps2, 478 }, services) 479 } 480 481 func TestCacheActionString(t *testing.T) { 482 require.Equal(t, "service-updated", UpdateService.String()) 483 require.Equal(t, "service-deleted", DeleteService.String()) 484 } 485 486 func TestServiceMutators(t *testing.T) { 487 var m1, m2 int 488 489 db, nodeAddrs := newDB(t) 490 svcCache := NewServiceCache(db, nodeAddrs) 491 492 svcCache.ServiceMutators = append(svcCache.ServiceMutators, 493 func(svc *slim_corev1.Service, svcInfo *Service) { m1++ }, 494 func(svc *slim_corev1.Service, svcInfo *Service) { m2++ }, 495 ) 496 swg := lock.NewStoppableWaitGroup() 497 svcCache.UpdateService(&slim_corev1.Service{ 498 ObjectMeta: slim_metav1.ObjectMeta{Name: "foo", Namespace: "bar"}, 499 Spec: slim_corev1.ServiceSpec{ 500 ClusterIP: "127.0.0.1", 501 Selector: map[string]string{"foo": "bar"}, 502 Type: slim_corev1.ServiceTypeClusterIP, 503 }, 504 }, swg) 505 506 // Assert that the service mutators configured have been executed. 507 require.Equal(t, 1, m1) 508 require.Equal(t, 1, m2) 509 } 510 511 func TestExternalServiceMerging(t *testing.T) { 512 db, nodeAddrs := newDB(t) 513 svcCache := NewServiceCache(db, nodeAddrs) 514 515 k8sSvc := &slim_corev1.Service{ 516 ObjectMeta: slim_metav1.ObjectMeta{ 517 Name: "foo", 518 Namespace: "bar", 519 Annotations: map[string]string{ 520 "service.cilium.io/global": "true", 521 }, 522 }, 523 Spec: slim_corev1.ServiceSpec{ 524 ClusterIP: "127.0.0.1", 525 Type: slim_corev1.ServiceTypeClusterIP, 526 Ports: []slim_corev1.ServicePort{ 527 { 528 Name: "foo", 529 Protocol: slim_corev1.ProtocolTCP, 530 Port: 80, 531 }, 532 }, 533 }, 534 } 535 536 swgSvcs := lock.NewStoppableWaitGroup() 537 svcID := svcCache.UpdateService(k8sSvc, swgSvcs) 538 539 endpoints := ParseEndpoints(&slim_corev1.Endpoints{ 540 ObjectMeta: slim_metav1.ObjectMeta{ 541 Name: "foo", 542 Namespace: "bar", 543 }, 544 Subsets: []slim_corev1.EndpointSubset{ 545 { 546 Addresses: []slim_corev1.EndpointAddress{{IP: "2.2.2.2"}}, 547 Ports: []slim_corev1.EndpointPort{ 548 { 549 Name: "http-test-svc", 550 Port: 8080, 551 Protocol: slim_corev1.ProtocolTCP, 552 }, 553 }, 554 }, 555 }, 556 }) 557 558 swgEps := lock.NewStoppableWaitGroup() 559 svcCache.UpdateEndpoints(endpoints, swgEps) 560 561 // The service should be ready as both service and endpoints have been 562 // imported 563 require.Nil(t, testutils.WaitUntil(func() bool { 564 event := <-svcCache.Events 565 defer event.SWG.Done() 566 require.Equal(t, UpdateService, event.Action) 567 require.Equal(t, svcID, event.ID) 568 return true 569 }, 2*time.Second)) 570 571 // Merging a service update with own cluster name must not result in update 572 svcCache.MergeExternalServiceUpdate(&serviceStore.ClusterService{ 573 Cluster: option.Config.ClusterName, 574 Namespace: "bar", 575 Name: "foo", 576 Frontends: map[string]serviceStore.PortConfiguration{ 577 "1.1.1.1": {}, 578 }, 579 Backends: map[string]serviceStore.PortConfiguration{ 580 "3.3.3.3": map[string]*loadbalancer.L4Addr{ 581 "port": {Protocol: loadbalancer.TCP, Port: 80}, 582 }, 583 }, 584 }, 585 swgSvcs, 586 ) 587 588 time.Sleep(100 * time.Millisecond) 589 590 select { 591 case <-svcCache.Events: 592 t.Error("Unexpected service event received") 593 default: 594 } 595 596 svcCache.MergeExternalServiceUpdate(&serviceStore.ClusterService{ 597 Cluster: "cluster1", 598 Namespace: "bar", 599 Name: "foo", 600 Frontends: map[string]serviceStore.PortConfiguration{ 601 "1.1.1.1": {}, 602 }, 603 Backends: map[string]serviceStore.PortConfiguration{ 604 "3.3.3.3": map[string]*loadbalancer.L4Addr{ 605 "port": {Protocol: loadbalancer.TCP, Port: 80}, 606 }, 607 }, 608 IncludeExternal: false, 609 Shared: false, 610 }, 611 swgSvcs, 612 ) 613 614 // Adding non-shared remote endpoints will not trigger a service update, regardless of whether 615 // IncludeExternal is set (i.e., the service is marked as a global one in the remote cluster). 616 require.Nil(t, testutils.WaitUntil(func() bool { 617 event := <-svcCache.Events 618 defer event.SWG.Done() 619 require.Equal(t, UpdateService, event.Action) 620 require.Equal(t, svcID, event.ID) 621 622 require.Equal(t, 1, len(event.Endpoints.Backends)) 623 require.EqualValues(t, &Backend{ 624 Ports: serviceStore.PortConfiguration{ 625 "http-test-svc": {Protocol: loadbalancer.TCP, Port: 8080}, 626 }, 627 }, event.Endpoints.Backends[cmtypes.MustParseAddrCluster("2.2.2.2")]) 628 629 return true 630 }, 2*time.Second)) 631 632 svcCache.MergeExternalServiceUpdate(&serviceStore.ClusterService{ 633 Cluster: "cluster1", 634 Namespace: "bar", 635 Name: "foo", 636 Frontends: map[string]serviceStore.PortConfiguration{ 637 "1.1.1.1": {}, 638 }, 639 Backends: map[string]serviceStore.PortConfiguration{ 640 "3.3.3.3": map[string]*loadbalancer.L4Addr{ 641 "port": {Protocol: loadbalancer.TCP, Port: 80}, 642 }, 643 }, 644 IncludeExternal: true, 645 Shared: false, 646 }, 647 swgSvcs, 648 ) 649 650 // Adding non-shared remote endpoints will not trigger a service update, regardless of whether 651 // IncludeExternal is set (i.e., the service is marked as a global one in the remote cluster). 652 require.Nil(t, testutils.WaitUntil(func() bool { 653 event := <-svcCache.Events 654 defer event.SWG.Done() 655 require.Equal(t, UpdateService, event.Action) 656 require.Equal(t, svcID, event.ID) 657 658 require.Equal(t, 1, len(event.Endpoints.Backends)) 659 require.EqualValues(t, &Backend{ 660 Ports: serviceStore.PortConfiguration{ 661 "http-test-svc": {Protocol: loadbalancer.TCP, Port: 8080}, 662 }, 663 }, event.Endpoints.Backends[cmtypes.MustParseAddrCluster("2.2.2.2")]) 664 665 return true 666 }, 2*time.Second)) 667 668 // We do not test the case with shared remote endpoints and IncludeExternal not set 669 // (i.e., the service is not marked as a global one in the remote cluster). 670 // Indeed, this condition shall never happen, since a shared service shall always be global. 671 672 svcCache.MergeExternalServiceUpdate(&serviceStore.ClusterService{ 673 Cluster: "cluster1", 674 Namespace: "bar", 675 Name: "foo", 676 Frontends: map[string]serviceStore.PortConfiguration{ 677 "1.1.1.1": {}, 678 }, 679 Backends: map[string]serviceStore.PortConfiguration{ 680 "3.3.3.3": map[string]*loadbalancer.L4Addr{ 681 "port": {Protocol: loadbalancer.TCP, Port: 80}, 682 }, 683 }, 684 IncludeExternal: true, 685 Shared: true, 686 }, 687 swgSvcs, 688 ) 689 690 // Adding shared remote endpoints will trigger a service update, in case IncludeExternal 691 // is set (i.e., the service is marked as a global one in the remote cluster). 692 require.Nil(t, testutils.WaitUntil(func() bool { 693 event := <-svcCache.Events 694 defer event.SWG.Done() 695 require.Equal(t, UpdateService, event.Action) 696 require.Equal(t, svcID, event.ID) 697 require.EqualValues(t, &Backend{ 698 Ports: serviceStore.PortConfiguration{ 699 "http-test-svc": {Protocol: loadbalancer.TCP, Port: 8080}, 700 }, 701 }, event.Endpoints.Backends[cmtypes.MustParseAddrCluster("2.2.2.2")]) 702 703 require.EqualValues(t, &Backend{ 704 Ports: serviceStore.PortConfiguration{ 705 "port": {Protocol: loadbalancer.TCP, Port: 80}, 706 }, 707 }, event.Endpoints.Backends[cmtypes.MustParseAddrCluster("3.3.3.3")]) 708 709 return true 710 }, 2*time.Second)) 711 712 // Merging a service for another name should not trigger any updates 713 svcCache.MergeExternalServiceUpdate(&serviceStore.ClusterService{ 714 Cluster: "cluster", 715 Namespace: "bar", 716 Name: "foo2", 717 Frontends: map[string]serviceStore.PortConfiguration{ 718 "1.1.1.1": {}, 719 }, 720 Backends: map[string]serviceStore.PortConfiguration{ 721 "3.3.3.3": map[string]*loadbalancer.L4Addr{ 722 "port": {Protocol: loadbalancer.TCP, Port: 80}, 723 }, 724 }, 725 IncludeExternal: true, 726 Shared: true, 727 }, 728 swgSvcs, 729 ) 730 731 time.Sleep(100 * time.Millisecond) 732 733 select { 734 case <-svcCache.Events: 735 t.Error("Unexpected service event received") 736 default: 737 } 738 739 // Adding the service later must trigger an update 740 svcID2 := svcCache.UpdateService( 741 &slim_corev1.Service{ 742 ObjectMeta: slim_metav1.ObjectMeta{ 743 Name: "foo2", 744 Namespace: "bar", 745 Labels: map[string]string{ 746 "foo": "bar", 747 }, 748 Annotations: map[string]string{ 749 "service.cilium.io/global": "true", 750 }, 751 }, 752 Spec: slim_corev1.ServiceSpec{ 753 ClusterIP: "127.0.0.2", 754 Selector: map[string]string{ 755 "foo": "bar", 756 }, 757 Type: slim_corev1.ServiceTypeClusterIP, 758 }, 759 }, 760 swgSvcs, 761 ) 762 763 require.Nil(t, testutils.WaitUntil(func() bool { 764 event := <-svcCache.Events 765 defer event.SWG.Done() 766 require.Equal(t, UpdateService, event.Action) 767 require.Equal(t, svcID2, event.ID) 768 return true 769 }, 2*time.Second)) 770 771 cluster2svc := &serviceStore.ClusterService{ 772 Cluster: "cluster2", 773 Namespace: "bar", 774 Name: "foo", 775 Frontends: map[string]serviceStore.PortConfiguration{ 776 "1.1.1.1": {}, 777 }, 778 Backends: map[string]serviceStore.PortConfiguration{ 779 "4.4.4.4": map[string]*loadbalancer.L4Addr{ 780 "port": {Protocol: loadbalancer.TCP, Port: 80}, 781 }, 782 }, 783 IncludeExternal: true, 784 Shared: true, 785 } 786 787 // Adding another cluster to the first service will trigger an event 788 svcCache.MergeExternalServiceUpdate(cluster2svc, swgSvcs) 789 require.Nil(t, testutils.WaitUntil(func() bool { 790 event := <-svcCache.Events 791 defer event.SWG.Done() 792 require.Equal(t, UpdateService, event.Action) 793 require.EqualValues(t, &Backend{ 794 Ports: serviceStore.PortConfiguration{ 795 "port": {Protocol: loadbalancer.TCP, Port: 80}, 796 }, 797 }, event.Endpoints.Backends[cmtypes.MustParseAddrCluster("4.4.4.4")]) 798 799 return true 800 }, 2*time.Second)) 801 802 svcCache.MergeExternalServiceDelete(cluster2svc, swgSvcs) 803 require.Nil(t, testutils.WaitUntil(func() bool { 804 event := <-svcCache.Events 805 defer event.SWG.Done() 806 require.Equal(t, UpdateService, event.Action) 807 require.Nil(t, event.Endpoints.Backends[cmtypes.MustParseAddrCluster("4.4.4.4")]) 808 return true 809 }, 2*time.Second)) 810 811 // Deletion of the service frontend will trigger the delete notification 812 svcCache.DeleteService(k8sSvc, swgSvcs) 813 require.Nil(t, testutils.WaitUntil(func() bool { 814 event := <-svcCache.Events 815 defer event.SWG.Done() 816 require.Equal(t, DeleteService, event.Action) 817 require.Equal(t, svcID, event.ID) 818 return true 819 }, 2*time.Second)) 820 821 // When re-adding the service, the remote endpoints of cluster1 must still be present 822 svcCache.UpdateService(k8sSvc, swgSvcs) 823 require.Nil(t, testutils.WaitUntil(func() bool { 824 event := <-svcCache.Events 825 defer event.SWG.Done() 826 require.Equal(t, UpdateService, event.Action) 827 require.Equal(t, svcID, event.ID) 828 require.EqualValues(t, &Backend{ 829 Ports: serviceStore.PortConfiguration{ 830 "port": {Protocol: loadbalancer.TCP, Port: 80}, 831 }, 832 }, event.Endpoints.Backends[cmtypes.MustParseAddrCluster("3.3.3.3")]) 833 return true 834 }, 2*time.Second)) 835 836 k8sSvcID, _ := ParseService(k8sSvc, nil) 837 addresses := svcCache.GetServiceIP(k8sSvcID) 838 require.EqualValues(t, loadbalancer.NewL3n4Addr(loadbalancer.TCP, cmtypes.MustParseAddrCluster("127.0.0.1"), 80, loadbalancer.ScopeExternal), addresses) 839 840 swgSvcs.Stop() 841 require.Nil(t, testutils.WaitUntil(func() bool { 842 swgSvcs.Wait() 843 return true 844 }, 2*time.Second)) 845 846 swgEps.Stop() 847 require.Nil(t, testutils.WaitUntil(func() bool { 848 swgEps.Wait() 849 return true 850 }, 2*time.Second)) 851 } 852 853 func TestExternalServiceDeletion(t *testing.T) { 854 const cluster = "cluster" 855 856 createEndpoints := func(clusters ...string) externalEndpoints { 857 eeps := newExternalEndpoints() 858 for i, cluster := range clusters { 859 eps := newEndpoints() 860 eps.Backends[cmtypes.MustParseAddrCluster(fmt.Sprintf("1.1.1.%d", i))] = &Backend{} 861 eeps.endpoints[cluster] = eps 862 } 863 864 return eeps 865 } 866 867 svc := Service{IncludeExternal: true, Shared: true} 868 clsvc := serviceStore.ClusterService{Cluster: cluster, Namespace: "bar", Name: "foo"} 869 id1 := ServiceID{Namespace: "bar", Name: "foo"} 870 id2 := ServiceID{Cluster: cluster, Namespace: "bar", Name: "foo"} 871 872 swg := lock.NewStoppableWaitGroup() 873 db, nodeAddrs := newDB(t) 874 svcCache := NewServiceCache(db, nodeAddrs) 875 876 // Store the service with the non-cluster-aware ID 877 svcCache.services[id1] = &svc 878 svcCache.externalEndpoints[id1] = createEndpoints(cluster) 879 880 svcCache.MergeExternalServiceDelete(&clsvc, swg) 881 _, ok := svcCache.services[id1] 882 require.Equal(t, false, ok) 883 _, ok = svcCache.externalEndpoints[id1] 884 require.Equal(t, false, ok) 885 886 require.Nil(t, testutils.WaitUntil(func() bool { 887 event := <-svcCache.Events 888 defer event.SWG.Done() 889 require.Equal(t, DeleteService, event.Action) 890 require.Equal(t, id1, event.ID) 891 return true 892 }, 2*time.Second)) 893 894 // Store the service with the non-cluster-aware ID and multiple endpoints 895 svcCache.services[id1] = &svc 896 svcCache.externalEndpoints[id1] = createEndpoints(cluster, "other") 897 898 svcCache.MergeExternalServiceDelete(&clsvc, swg) 899 _, ok = svcCache.services[id1] 900 require.Equal(t, true, ok) 901 _, ok = svcCache.externalEndpoints[id1] 902 require.Equal(t, true, ok) 903 _, ok = svcCache.externalEndpoints[id1].endpoints[cluster] 904 require.Equal(t, false, ok) 905 906 require.Nil(t, testutils.WaitUntil(func() bool { 907 event := <-svcCache.Events 908 defer event.SWG.Done() 909 require.Equal(t, UpdateService, event.Action) 910 require.Equal(t, id1, event.ID) 911 return true 912 }, 2*time.Second)) 913 914 // Store the service with the cluster-aware ID 915 svcCache.services[id2] = &svc 916 svcCache.externalEndpoints[id2] = createEndpoints(cluster) 917 918 svcCache.MergeExternalServiceDelete(&clsvc, swg) 919 _, ok = svcCache.services[id2] 920 require.Equal(t, false, ok) 921 _, ok = svcCache.externalEndpoints[id2] 922 require.Equal(t, false, ok) 923 924 require.Nil(t, testutils.WaitUntil(func() bool { 925 event := <-svcCache.Events 926 defer event.SWG.Done() 927 require.Equal(t, DeleteService, event.Action) 928 require.Equal(t, id2, event.ID) 929 return true 930 }, 2*time.Second)) 931 } 932 933 func TestClusterServiceMerging(t *testing.T) { 934 db, nodeAddrs := newDB(t) 935 svcCache := NewServiceCache(db, nodeAddrs) 936 swgSvcs := lock.NewStoppableWaitGroup() 937 swgEps := lock.NewStoppableWaitGroup() 938 939 svcID := ServiceID{Name: "foo", Namespace: "bar"} 940 941 endpoints := ParseEndpoints(&slim_corev1.Endpoints{ 942 ObjectMeta: slim_metav1.ObjectMeta{ 943 Namespace: svcID.Namespace, 944 Name: svcID.Name, 945 }, 946 Subsets: []slim_corev1.EndpointSubset{ 947 { 948 Addresses: []slim_corev1.EndpointAddress{{IP: "2.2.2.2"}}, 949 Ports: []slim_corev1.EndpointPort{ 950 { 951 Name: "http-test-svc", 952 Port: 8080, 953 Protocol: slim_corev1.ProtocolTCP, 954 }, 955 }, 956 }, 957 }, 958 }) 959 960 svcCache.UpdateEndpoints(endpoints, swgEps) 961 962 svcCache.MergeClusterServiceUpdate(&serviceStore.ClusterService{ 963 Cluster: option.Config.ClusterName, 964 Namespace: svcID.Namespace, 965 Name: svcID.Name, 966 Frontends: map[string]serviceStore.PortConfiguration{ 967 "1.1.1.1": {}, 968 }, 969 Backends: map[string]serviceStore.PortConfiguration{ 970 "3.3.3.3": map[string]*loadbalancer.L4Addr{ 971 "port": {Protocol: loadbalancer.TCP, Port: 80}, 972 }, 973 }, 974 IncludeExternal: false, 975 Shared: false, 976 }, swgSvcs) 977 978 // Adding a service will trigger the corresponding update containing all ready backends, 979 // regardless of whether it is marked as global or shared (since the cluster name matches). 980 require.Nil(t, testutils.WaitUntil(func() bool { 981 event := <-svcCache.Events 982 defer event.SWG.Done() 983 require.Equal(t, UpdateService, event.Action) 984 require.Equal(t, svcID, event.ID) 985 require.EqualValues(t, &Backend{ 986 Ports: serviceStore.PortConfiguration{ 987 "http-test-svc": {Protocol: loadbalancer.TCP, Port: 8080}, 988 }, 989 }, event.Endpoints.Backends[cmtypes.MustParseAddrCluster("2.2.2.2")]) 990 991 require.EqualValues(t, &Backend{ 992 Ports: serviceStore.PortConfiguration{ 993 "port": {Protocol: loadbalancer.TCP, Port: 80}, 994 }, 995 }, event.Endpoints.Backends[cmtypes.MustParseAddrCluster("3.3.3.3")]) 996 997 return true 998 }, 2*time.Second)) 999 } 1000 1001 func TestNonSharedService(t *testing.T) { 1002 db, nodeAddrs := newDB(t) 1003 svcCache := NewServiceCache(db, nodeAddrs) 1004 1005 k8sSvc := &slim_corev1.Service{ 1006 ObjectMeta: slim_metav1.ObjectMeta{ 1007 Name: "foo", 1008 Namespace: "bar", 1009 Annotations: map[string]string{ 1010 "service.cilium.io/global": "false", 1011 }, 1012 }, 1013 Spec: slim_corev1.ServiceSpec{ 1014 ClusterIP: "127.0.0.1", 1015 Type: slim_corev1.ServiceTypeClusterIP, 1016 }, 1017 } 1018 1019 swgSvcs := lock.NewStoppableWaitGroup() 1020 svcCache.UpdateService(k8sSvc, swgSvcs) 1021 1022 svcCache.MergeExternalServiceUpdate(&serviceStore.ClusterService{ 1023 Cluster: "cluster1", 1024 Namespace: "bar", 1025 Name: "foo", 1026 Backends: map[string]serviceStore.PortConfiguration{ 1027 "3.3.3.3": map[string]*loadbalancer.L4Addr{ 1028 "port": {Protocol: loadbalancer.TCP, Port: 80}, 1029 }, 1030 }, 1031 }, 1032 swgSvcs, 1033 ) 1034 1035 // The service is unshared, it should not trigger an update 1036 time.Sleep(100 * time.Millisecond) 1037 1038 select { 1039 case <-svcCache.Events: 1040 t.Error("Unexpected service event received") 1041 default: 1042 } 1043 1044 swgSvcs.Stop() 1045 require.Nil(t, testutils.WaitUntil(func() bool { 1046 swgSvcs.Wait() 1047 return true 1048 }, 2*time.Second)) 1049 } 1050 1051 func TestServiceCacheWith2EndpointSlice(t *testing.T) { 1052 k8sEndpointSlice1 := ParseEndpointSliceV1(&slim_discovery_v1.EndpointSlice{ 1053 AddressType: slim_discovery_v1.AddressTypeIPv4, 1054 ObjectMeta: slim_metav1.ObjectMeta{ 1055 Name: "foo-yyyyy", 1056 Namespace: "bar", 1057 Labels: map[string]string{ 1058 slim_discovery_v1.LabelServiceName: "foo", 1059 }, 1060 }, 1061 Endpoints: []slim_discovery_v1.Endpoint{ 1062 { 1063 Addresses: []string{ 1064 "2.2.2.2", 1065 }, 1066 }, 1067 }, 1068 Ports: []slim_discovery_v1.EndpointPort{ 1069 { 1070 Name: func() *string { a := "http-test-svc"; return &a }(), 1071 Protocol: func() *slim_corev1.Protocol { a := slim_corev1.ProtocolTCP; return &a }(), 1072 Port: func() *int32 { a := int32(8080); return &a }(), 1073 }, 1074 }, 1075 }) 1076 1077 k8sEndpointSlice2 := ParseEndpointSliceV1(&slim_discovery_v1.EndpointSlice{ 1078 AddressType: slim_discovery_v1.AddressTypeIPv4, 1079 ObjectMeta: slim_metav1.ObjectMeta{ 1080 Name: "foo-xxxxx", 1081 Namespace: "bar", 1082 Labels: map[string]string{ 1083 slim_discovery_v1.LabelServiceName: "foo", 1084 }, 1085 }, 1086 Endpoints: []slim_discovery_v1.Endpoint{ 1087 { 1088 Addresses: []string{ 1089 "2.2.2.3", 1090 }, 1091 }, 1092 }, 1093 Ports: []slim_discovery_v1.EndpointPort{ 1094 { 1095 Name: func() *string { a := "http-test-svc"; return &a }(), 1096 Protocol: func() *slim_corev1.Protocol { a := slim_corev1.ProtocolTCP; return &a }(), 1097 Port: func() *int32 { a := int32(8080); return &a }(), 1098 }, 1099 }, 1100 }) 1101 1102 k8sEndpointSlice3 := ParseEndpointSliceV1(&slim_discovery_v1.EndpointSlice{ 1103 AddressType: slim_discovery_v1.AddressTypeIPv4, 1104 ObjectMeta: slim_metav1.ObjectMeta{ 1105 Name: "foo-xxxxx", 1106 Namespace: "baz", 1107 Labels: map[string]string{ 1108 slim_discovery_v1.LabelServiceName: "foo", 1109 }, 1110 }, 1111 Endpoints: []slim_discovery_v1.Endpoint{ 1112 { 1113 Addresses: []string{ 1114 "2.2.2.4", 1115 }, 1116 }, 1117 }, 1118 Ports: []slim_discovery_v1.EndpointPort{ 1119 { 1120 Name: func() *string { a := "http-test-svc"; return &a }(), 1121 Protocol: func() *slim_corev1.Protocol { a := slim_corev1.ProtocolTCP; return &a }(), 1122 Port: func() *int32 { a := int32(8080); return &a }(), 1123 }, 1124 }, 1125 }) 1126 1127 db, nodeAddrs := newDB(t) 1128 svcCache := NewServiceCache(db, nodeAddrs) 1129 1130 k8sSvc := &slim_corev1.Service{ 1131 ObjectMeta: slim_metav1.ObjectMeta{ 1132 Name: "foo", 1133 Namespace: "bar", 1134 Labels: map[string]string{ 1135 "foo": "bar", 1136 }, 1137 }, 1138 Spec: slim_corev1.ServiceSpec{ 1139 ClusterIP: "127.0.0.1", 1140 Selector: map[string]string{ 1141 "foo": "bar", 1142 }, 1143 Type: slim_corev1.ServiceTypeClusterIP, 1144 }, 1145 } 1146 1147 swgSvcs := lock.NewStoppableWaitGroup() 1148 svcID := svcCache.UpdateService(k8sSvc, swgSvcs) 1149 1150 time.Sleep(100 * time.Millisecond) 1151 1152 select { 1153 case <-svcCache.Events: 1154 t.Error("Unexpected service event received before endpoints have been imported") 1155 default: 1156 } 1157 1158 swgEps := lock.NewStoppableWaitGroup() 1159 svcCache.UpdateEndpoints(k8sEndpointSlice1, swgEps) 1160 svcCache.UpdateEndpoints(k8sEndpointSlice2, swgEps) 1161 svcCache.UpdateEndpoints(k8sEndpointSlice3, swgEps) 1162 1163 // The service should be ready as both service and endpoints have been 1164 // imported for k8sEndpointSlice1 1165 require.Nil(t, testutils.WaitUntil(func() bool { 1166 event := <-svcCache.Events 1167 defer event.SWG.Done() 1168 require.Equal(t, UpdateService, event.Action) 1169 require.Equal(t, svcID, event.ID) 1170 return true 1171 }, 2*time.Second)) 1172 1173 // The service should be ready as both service and endpoints have been 1174 // imported for k8sEndpointSlice2 1175 require.Nil(t, testutils.WaitUntil(func() bool { 1176 event := <-svcCache.Events 1177 defer event.SWG.Done() 1178 require.Equal(t, UpdateService, event.Action) 1179 require.Equal(t, svcID, event.ID) 1180 return true 1181 }, 2*time.Second)) 1182 1183 select { 1184 case <-svcCache.Events: 1185 t.Error("Unexpected service event received when endpoints not selected by a service have been imported") 1186 default: 1187 } 1188 endpoints, ready := svcCache.correlateEndpoints(svcID) 1189 require.Equal(t, true, ready) 1190 require.Equal(t, "2.2.2.2:8080/TCP,2.2.2.3:8080/TCP", endpoints.String()) 1191 1192 // Updating the service without changing it should not result in an event 1193 svcCache.UpdateService(k8sSvc, swgSvcs) 1194 time.Sleep(100 * time.Millisecond) 1195 select { 1196 case <-svcCache.Events: 1197 t.Error("Unexpected service event received for unchanged service object") 1198 default: 1199 } 1200 1201 // Deleting the service will result in a service delete event 1202 svcCache.DeleteService(k8sSvc, swgSvcs) 1203 require.Nil(t, testutils.WaitUntil(func() bool { 1204 event := <-svcCache.Events 1205 defer event.SWG.Done() 1206 require.Equal(t, DeleteService, event.Action) 1207 require.Equal(t, svcID, event.ID) 1208 return true 1209 }, 2*time.Second)) 1210 1211 // Reinserting the service should re-match with the still existing endpoints 1212 svcCache.UpdateService(k8sSvc, swgSvcs) 1213 require.Nil(t, testutils.WaitUntil(func() bool { 1214 event := <-svcCache.Events 1215 defer event.SWG.Done() 1216 require.Equal(t, UpdateService, event.Action) 1217 require.Equal(t, svcID, event.ID) 1218 return true 1219 }, 2*time.Second)) 1220 1221 // Deleting the k8sEndpointSlice2 will result in a service update event 1222 svcCache.DeleteEndpoints(k8sEndpointSlice2.EndpointSliceID, swgEps) 1223 require.Nil(t, testutils.WaitUntil(func() bool { 1224 event := <-svcCache.Events 1225 defer event.SWG.Done() 1226 require.Equal(t, UpdateService, event.Action) 1227 require.Equal(t, svcID, event.ID) 1228 return true 1229 }, 2*time.Second)) 1230 1231 endpoints, ready = svcCache.correlateEndpoints(svcID) 1232 require.Equal(t, true, ready) 1233 require.Equal(t, "2.2.2.2:8080/TCP", endpoints.String()) 1234 1235 svcCache.DeleteEndpoints(k8sEndpointSlice1.EndpointSliceID, swgEps) 1236 require.Nil(t, testutils.WaitUntil(func() bool { 1237 event := <-svcCache.Events 1238 defer event.SWG.Done() 1239 require.Equal(t, UpdateService, event.Action) 1240 require.Equal(t, svcID, event.ID) 1241 return true 1242 }, 2*time.Second)) 1243 1244 endpoints, serviceReady := svcCache.correlateEndpoints(svcID) 1245 require.Equal(t, false, serviceReady) 1246 require.Equal(t, "", endpoints.String()) 1247 1248 // Reinserting the endpoints should re-match with the still existing service 1249 svcCache.UpdateEndpoints(k8sEndpointSlice1, swgEps) 1250 require.Nil(t, testutils.WaitUntil(func() bool { 1251 event := <-svcCache.Events 1252 defer event.SWG.Done() 1253 require.Equal(t, UpdateService, event.Action) 1254 require.Equal(t, svcID, event.ID) 1255 return true 1256 }, 2*time.Second)) 1257 1258 endpoints, serviceReady = svcCache.correlateEndpoints(svcID) 1259 require.Equal(t, true, serviceReady) 1260 require.Equal(t, "2.2.2.2:8080/TCP", endpoints.String()) 1261 1262 // Deleting the service will result in a service delete event 1263 svcCache.DeleteService(k8sSvc, swgSvcs) 1264 require.Nil(t, testutils.WaitUntil(func() bool { 1265 event := <-svcCache.Events 1266 defer event.SWG.Done() 1267 require.Equal(t, DeleteService, event.Action) 1268 require.Equal(t, svcID, event.ID) 1269 return true 1270 }, 2*time.Second)) 1271 1272 // Deleting the endpoints will not emit an event as the notification 1273 // was sent out when the service was deleted. 1274 svcCache.DeleteEndpoints(k8sEndpointSlice1.EndpointSliceID, swgEps) 1275 time.Sleep(100 * time.Millisecond) 1276 select { 1277 case <-svcCache.Events: 1278 t.Error("Unexpected service delete event received") 1279 default: 1280 } 1281 1282 swgSvcs.Stop() 1283 require.Nil(t, testutils.WaitUntil(func() bool { 1284 swgSvcs.Wait() 1285 return true 1286 }, 2*time.Second)) 1287 1288 swgEps.Stop() 1289 require.Nil(t, testutils.WaitUntil(func() bool { 1290 swgEps.Wait() 1291 return true 1292 }, 2*time.Second)) 1293 } 1294 1295 func TestServiceCacheWith2EndpointSliceSameAddress(t *testing.T) { 1296 k8sEndpointSlice1 := ParseEndpointSliceV1(&slim_discovery_v1.EndpointSlice{ 1297 AddressType: slim_discovery_v1.AddressTypeIPv4, 1298 ObjectMeta: slim_metav1.ObjectMeta{ 1299 Name: "foo-yyyyy", 1300 Namespace: "bar", 1301 Labels: map[string]string{ 1302 slim_discovery_v1.LabelServiceName: "foo", 1303 }, 1304 }, 1305 Endpoints: []slim_discovery_v1.Endpoint{ 1306 { 1307 Addresses: []string{ 1308 "2.2.2.2", 1309 }, 1310 }, 1311 }, 1312 Ports: []slim_discovery_v1.EndpointPort{ 1313 { 1314 Name: func() *string { a := "http-test-svc"; return &a }(), 1315 Protocol: func() *slim_corev1.Protocol { a := slim_corev1.ProtocolTCP; return &a }(), 1316 Port: func() *int32 { a := int32(8080); return &a }(), 1317 }, 1318 }, 1319 }) 1320 1321 k8sEndpointSlice2 := ParseEndpointSliceV1(&slim_discovery_v1.EndpointSlice{ 1322 AddressType: slim_discovery_v1.AddressTypeIPv4, 1323 ObjectMeta: slim_metav1.ObjectMeta{ 1324 Name: "foo-xxxxx", 1325 Namespace: "bar", 1326 Labels: map[string]string{ 1327 slim_discovery_v1.LabelServiceName: "foo", 1328 }, 1329 }, 1330 Endpoints: []slim_discovery_v1.Endpoint{ 1331 { 1332 Addresses: []string{ 1333 "2.2.2.2", 1334 }, 1335 }, 1336 }, 1337 Ports: []slim_discovery_v1.EndpointPort{ 1338 { 1339 Name: func() *string { a := "http-test-svc2"; return &a }(), 1340 Protocol: func() *slim_corev1.Protocol { a := slim_corev1.ProtocolTCP; return &a }(), 1341 Port: func() *int32 { a := int32(8081); return &a }(), 1342 }, 1343 }, 1344 }) 1345 1346 db, nodeAddrs := newDB(t) 1347 svcCache := NewServiceCache(db, nodeAddrs) 1348 1349 k8sSvc := &slim_corev1.Service{ 1350 ObjectMeta: slim_metav1.ObjectMeta{ 1351 Name: "foo", 1352 Namespace: "bar", 1353 Labels: map[string]string{ 1354 "foo": "bar", 1355 }, 1356 }, 1357 Spec: slim_corev1.ServiceSpec{ 1358 ClusterIP: "127.0.0.1", 1359 Selector: map[string]string{ 1360 "foo": "bar", 1361 }, 1362 Type: slim_corev1.ServiceTypeClusterIP, 1363 }, 1364 } 1365 1366 swgSvcs := lock.NewStoppableWaitGroup() 1367 svcID := svcCache.UpdateService(k8sSvc, swgSvcs) 1368 1369 time.Sleep(100 * time.Millisecond) 1370 1371 select { 1372 case <-svcCache.Events: 1373 t.Error("Unexpected service event received before endpoints have been imported") 1374 default: 1375 } 1376 1377 swgEps := lock.NewStoppableWaitGroup() 1378 svcCache.UpdateEndpoints(k8sEndpointSlice1, swgEps) 1379 svcCache.UpdateEndpoints(k8sEndpointSlice2, swgEps) 1380 1381 // The service should be ready as both service and endpoints have been 1382 // imported for k8sEndpointSlice1 1383 require.Nil(t, testutils.WaitUntil(func() bool { 1384 event := <-svcCache.Events 1385 defer event.SWG.Done() 1386 require.Equal(t, UpdateService, event.Action) 1387 require.Equal(t, svcID, event.ID) 1388 return true 1389 }, 2*time.Second)) 1390 1391 // The service should be ready as both service and endpoints have been 1392 // imported for k8sEndpointSlice2 1393 require.Nil(t, testutils.WaitUntil(func() bool { 1394 event := <-svcCache.Events 1395 defer event.SWG.Done() 1396 require.Equal(t, UpdateService, event.Action) 1397 require.Equal(t, svcID, event.ID) 1398 return true 1399 }, 2*time.Second)) 1400 1401 select { 1402 case <-svcCache.Events: 1403 t.Error("Unexpected service event received when endpoints not selected by a service have been imported") 1404 default: 1405 } 1406 endpoints, ready := svcCache.correlateEndpoints(svcID) 1407 require.Equal(t, true, ready) 1408 require.Equal(t, "2.2.2.2:8080/TCP,2.2.2.2:8081/TCP", endpoints.String()) 1409 1410 // Updating the service without changing it should not result in an event 1411 svcCache.UpdateService(k8sSvc, swgSvcs) 1412 time.Sleep(100 * time.Millisecond) 1413 select { 1414 case <-svcCache.Events: 1415 t.Error("Unexpected service event received for unchanged service object") 1416 default: 1417 } 1418 1419 // Deleting the service will result in a service delete event 1420 svcCache.DeleteService(k8sSvc, swgSvcs) 1421 require.Nil(t, testutils.WaitUntil(func() bool { 1422 event := <-svcCache.Events 1423 defer event.SWG.Done() 1424 require.Equal(t, DeleteService, event.Action) 1425 require.Equal(t, svcID, event.ID) 1426 return true 1427 }, 2*time.Second)) 1428 1429 // Reinserting the service should re-match with the still existing endpoints 1430 svcCache.UpdateService(k8sSvc, swgSvcs) 1431 require.Nil(t, testutils.WaitUntil(func() bool { 1432 event := <-svcCache.Events 1433 defer event.SWG.Done() 1434 require.Equal(t, UpdateService, event.Action) 1435 require.Equal(t, svcID, event.ID) 1436 return true 1437 }, 2*time.Second)) 1438 1439 // Deleting the k8sEndpointSlice2 will result in a service update event 1440 svcCache.DeleteEndpoints(k8sEndpointSlice2.EndpointSliceID, swgEps) 1441 require.Nil(t, testutils.WaitUntil(func() bool { 1442 event := <-svcCache.Events 1443 defer event.SWG.Done() 1444 require.Equal(t, UpdateService, event.Action) 1445 require.Equal(t, svcID, event.ID) 1446 return true 1447 }, 2*time.Second)) 1448 1449 endpoints, ready = svcCache.correlateEndpoints(svcID) 1450 require.Equal(t, true, ready) 1451 require.Equal(t, "2.2.2.2:8080/TCP", endpoints.String()) 1452 1453 svcCache.DeleteEndpoints(k8sEndpointSlice1.EndpointSliceID, swgEps) 1454 require.Nil(t, testutils.WaitUntil(func() bool { 1455 event := <-svcCache.Events 1456 defer event.SWG.Done() 1457 require.Equal(t, UpdateService, event.Action) 1458 require.Equal(t, svcID, event.ID) 1459 return true 1460 }, 2*time.Second)) 1461 1462 endpoints, serviceReady := svcCache.correlateEndpoints(svcID) 1463 require.Equal(t, false, serviceReady) 1464 require.Equal(t, "", endpoints.String()) 1465 1466 // Reinserting the endpoints should re-match with the still existing service 1467 svcCache.UpdateEndpoints(k8sEndpointSlice1, swgEps) 1468 require.Nil(t, testutils.WaitUntil(func() bool { 1469 event := <-svcCache.Events 1470 defer event.SWG.Done() 1471 require.Equal(t, UpdateService, event.Action) 1472 require.Equal(t, svcID, event.ID) 1473 return true 1474 }, 2*time.Second)) 1475 1476 endpoints, serviceReady = svcCache.correlateEndpoints(svcID) 1477 require.Equal(t, true, serviceReady) 1478 require.Equal(t, "2.2.2.2:8080/TCP", endpoints.String()) 1479 1480 // Deleting the service will result in a service delete event 1481 svcCache.DeleteService(k8sSvc, swgSvcs) 1482 require.Nil(t, testutils.WaitUntil(func() bool { 1483 event := <-svcCache.Events 1484 defer event.SWG.Done() 1485 require.Equal(t, DeleteService, event.Action) 1486 require.Equal(t, svcID, event.ID) 1487 return true 1488 }, 2*time.Second)) 1489 1490 // Deleting the endpoints will not emit an event as the notification 1491 // was sent out when the service was deleted. 1492 svcCache.DeleteEndpoints(k8sEndpointSlice1.EndpointSliceID, swgEps) 1493 time.Sleep(100 * time.Millisecond) 1494 select { 1495 case <-svcCache.Events: 1496 t.Error("Unexpected service delete event received") 1497 default: 1498 } 1499 1500 swgSvcs.Stop() 1501 require.Nil(t, testutils.WaitUntil(func() bool { 1502 swgSvcs.Wait() 1503 return true 1504 }, 2*time.Second)) 1505 1506 swgEps.Stop() 1507 require.Nil(t, testutils.WaitUntil(func() bool { 1508 swgEps.Wait() 1509 return true 1510 }, 2*time.Second)) 1511 } 1512 1513 func TestServiceEndpointFiltering(t *testing.T) { 1514 k8sSvc := &slim_corev1.Service{ 1515 ObjectMeta: slim_metav1.ObjectMeta{ 1516 Name: "foo", 1517 Namespace: "bar", 1518 Labels: map[string]string{"foo": "bar"}, 1519 Annotations: map[string]string{ 1520 v1.DeprecatedAnnotationTopologyAwareHints: "auto", 1521 }, 1522 }, 1523 Spec: slim_corev1.ServiceSpec{ 1524 ClusterIP: "127.0.0.1", 1525 Selector: map[string]string{"foo": "bar"}, 1526 Type: slim_corev1.ServiceTypeClusterIP, 1527 }, 1528 } 1529 veryTrue := true 1530 k8sEndpointSlice := ParseEndpointSliceV1(&slim_discovery_v1.EndpointSlice{ 1531 AddressType: slim_discovery_v1.AddressTypeIPv4, 1532 ObjectMeta: slim_metav1.ObjectMeta{ 1533 Name: "foo-ep-filtering", 1534 Namespace: "bar", 1535 Labels: map[string]string{ 1536 slim_discovery_v1.LabelServiceName: "foo", 1537 }, 1538 }, 1539 Endpoints: []slim_discovery_v1.Endpoint{ 1540 { 1541 Addresses: []string{"10.0.0.1"}, 1542 Hints: &slim_discovery_v1.EndpointHints{ 1543 ForZones: []slim_discovery_v1.ForZone{{Name: "test-zone-1"}}, 1544 }, 1545 Conditions: slim_discovery_v1.EndpointConditions{Ready: &veryTrue}, 1546 }, 1547 { 1548 Addresses: []string{"10.0.0.2"}, 1549 Hints: &slim_discovery_v1.EndpointHints{ 1550 ForZones: []slim_discovery_v1.ForZone{{Name: "test-zone-2"}}, 1551 }, 1552 Conditions: slim_discovery_v1.EndpointConditions{Ready: &veryTrue}, 1553 }, 1554 }, 1555 }) 1556 1557 store := node.NewTestLocalNodeStore(node.LocalNode{Node: types.Node{ 1558 Labels: map[string]string{v1.LabelTopologyZone: "test-zone-2"}, 1559 }}) 1560 db, nodeAddrs := newDB(t) 1561 svcCache := newServiceCache(hivetest.Lifecycle(t), 1562 ServiceCacheConfig{EnableServiceTopology: true}, store, 1563 db, nodeAddrs) 1564 1565 swg := lock.NewStoppableWaitGroup() 1566 1567 // Now update service and endpointslice. This should result in the service 1568 // update with 2.2.2.2 endpoint due to the zone filtering. 1569 svcID0 := svcCache.UpdateService(k8sSvc, swg) 1570 svcID1, eps := svcCache.UpdateEndpoints(k8sEndpointSlice, swg) 1571 require.Equal(t, svcID1, svcID0) 1572 require.Equal(t, 1, len(eps.Backends)) 1573 require.Nil(t, testutils.WaitUntil(func() bool { 1574 event := <-svcCache.Events 1575 require.Equal(t, UpdateService, event.Action) 1576 require.Equal(t, svcID0, event.ID) 1577 require.Equal(t, 1, len(event.Endpoints.Backends)) 1578 _, found := event.Endpoints.Backends[cmtypes.MustParseAddrCluster("10.0.0.2")] 1579 require.Equal(t, true, found) 1580 return true 1581 }, 2*time.Second)) 1582 1583 // Send self node update to remove the node's zone label. This should 1584 // generate the service update with both endpoints selected 1585 store.Update(func(ln *node.LocalNode) { ln.Labels = nil }) 1586 require.Nil(t, testutils.WaitUntil(func() bool { 1587 event := <-svcCache.Events 1588 require.Equal(t, UpdateService, event.Action) 1589 require.Equal(t, svcID0, event.ID) 1590 require.Equal(t, 2, len(event.Endpoints.Backends)) 1591 return true 1592 }, 2*time.Second)) 1593 1594 // Set the node's zone to test-zone-1 to select the first endpoint 1595 store.Update(func(ln *node.LocalNode) { ln.Labels = map[string]string{v1.LabelTopologyZone: "test-zone-1"} }) 1596 require.Nil(t, testutils.WaitUntil(func() bool { 1597 event := <-svcCache.Events 1598 require.Equal(t, UpdateService, event.Action) 1599 require.Equal(t, svcID0, event.ID) 1600 require.Equal(t, 1, len(event.Endpoints.Backends)) 1601 _, found := event.Endpoints.Backends[cmtypes.MustParseAddrCluster("10.0.0.1")] 1602 require.Equal(t, true, found) 1603 return true 1604 }, 2*time.Second)) 1605 1606 // Removing the service annotation should have no effect as long as EndpointSlice hints are set 1607 k8sSvc.ObjectMeta.Annotations = nil 1608 svcID0 = svcCache.UpdateService(k8sSvc, swg) 1609 require.Nil(t, testutils.WaitUntil(func() bool { 1610 event := <-svcCache.Events 1611 require.Equal(t, UpdateService, event.Action) 1612 require.Equal(t, svcID0, event.ID) 1613 require.Equal(t, 1, len(event.Endpoints.Backends)) 1614 return true 1615 }, 2*time.Second)) 1616 1617 // Remove the zone hints. This should select all endpoints 1618 k8sEndpointSlice = k8sEndpointSlice.DeepCopy() 1619 for _, be := range k8sEndpointSlice.Backends { 1620 be.HintsForZones = nil 1621 } 1622 svcID1, _ = svcCache.UpdateEndpoints(k8sEndpointSlice, swg) 1623 require.Nil(t, testutils.WaitUntil(func() bool { 1624 event := <-svcCache.Events 1625 require.Equal(t, UpdateService, event.Action) 1626 require.Equal(t, svcID1, event.ID) 1627 require.Equal(t, 2, len(event.Endpoints.Backends)) 1628 return true 1629 }, 2*time.Second)) 1630 }