istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/xds/ads_test.go (about) 1 // Copyright Istio Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 package xds_test 15 16 import ( 17 "fmt" 18 "reflect" 19 "testing" 20 "time" 21 22 core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" 23 discovery "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3" 24 25 networking "istio.io/api/networking/v1alpha3" 26 "istio.io/istio/pilot/pkg/features" 27 "istio.io/istio/pilot/pkg/model" 28 "istio.io/istio/pilot/pkg/status/distribution" 29 "istio.io/istio/pilot/pkg/xds" 30 v3 "istio.io/istio/pilot/pkg/xds/v3" 31 xdsfake "istio.io/istio/pilot/test/xds" 32 "istio.io/istio/pilot/test/xdstest" 33 "istio.io/istio/pkg/adsc" 34 "istio.io/istio/pkg/config" 35 "istio.io/istio/pkg/config/host" 36 "istio.io/istio/pkg/config/protocol" 37 "istio.io/istio/pkg/config/schema/gvk" 38 "istio.io/istio/pkg/config/schema/kind" 39 "istio.io/istio/pkg/ledger" 40 "istio.io/istio/pkg/slices" 41 "istio.io/istio/pkg/test" 42 "istio.io/istio/pkg/test/util/assert" 43 "istio.io/istio/pkg/test/util/retry" 44 "istio.io/istio/pkg/util/sets" 45 "istio.io/istio/tests/util/leak" 46 ) 47 48 const ( 49 testConfigNamespace = "default" 50 51 routeA = "http.80" 52 routeB = "https.443.https.my-gateway.testns" 53 ) 54 55 func TestAdsReconnectAfterRestart(t *testing.T) { 56 s := xdsfake.NewFakeDiscoveryServer(t, xdsfake.FakeOptions{}) 57 58 ads := s.ConnectADS().WithType(v3.EndpointType) 59 res := ads.RequestResponseAck(t, &discovery.DiscoveryRequest{ResourceNames: []string{"fake-cluster"}}) 60 // Close the connection and reconnect 61 ads.Cleanup() 62 63 ads = s.ConnectADS().WithType(v3.EndpointType) 64 65 // Reconnect with the same resources 66 ads.RequestResponseAck(t, &discovery.DiscoveryRequest{ 67 ResourceNames: []string{"fake-cluster"}, 68 ResponseNonce: res.Nonce, 69 VersionInfo: res.VersionInfo, 70 }) 71 } 72 73 // TestAdsReconnectRequests provides a regression test for a case where Envoy sends an EDS request as the first 74 // request on a connection. 75 func TestAdsReconnectRequests(t *testing.T) { 76 s := xdsfake.NewFakeDiscoveryServer(t, xdsfake.FakeOptions{}) 77 78 ads := s.ConnectADS() 79 // Send normal CDS and EDS requests 80 _ = ads.RequestResponseAck(t, &discovery.DiscoveryRequest{TypeUrl: v3.ClusterType}) 81 eres := ads.RequestResponseAck(t, &discovery.DiscoveryRequest{TypeUrl: v3.EndpointType, ResourceNames: []string{"my-resource"}}) 82 83 // A push should get a response for both 84 s.Discovery.ConfigUpdate(&model.PushRequest{Full: true}) 85 ads.ExpectResponse(t) 86 ads.ExpectResponse(t) 87 // Close the connection and reconnect 88 ads.Cleanup() 89 ads = s.ConnectADS() 90 91 // Send a request for EDS version 1 - we do not explicitly ACK this. 92 ads.Request(t, &discovery.DiscoveryRequest{ 93 TypeUrl: v3.EndpointType, 94 ResourceNames: []string{"my-resource"}, 95 ResponseNonce: eres.Nonce, 96 }) 97 // We should get a response 98 eres3 := ads.ExpectResponse(t) 99 // Now send our CDS request 100 ads.RequestResponseAck(t, &discovery.DiscoveryRequest{ 101 TypeUrl: v3.ClusterType, 102 ResponseNonce: eres.Nonce, 103 }) 104 // Send another request. This is essentially an ACK of eres3. However, envoy expects a response 105 ads.RequestResponseAck(t, &discovery.DiscoveryRequest{ 106 TypeUrl: v3.EndpointType, 107 ResourceNames: []string{"my-resource"}, 108 ResponseNonce: eres3.Nonce, 109 }) 110 } 111 112 func TestAdsUnsubscribe(t *testing.T) { 113 s := xdsfake.NewFakeDiscoveryServer(t, xdsfake.FakeOptions{}) 114 115 ads := s.ConnectADS().WithType(v3.EndpointType) 116 res := ads.RequestResponseAck(t, &discovery.DiscoveryRequest{ResourceNames: []string{"fake-cluster"}}) 117 118 ads.Request(t, &discovery.DiscoveryRequest{ 119 ResourceNames: nil, 120 ResponseNonce: res.Nonce, 121 VersionInfo: res.VersionInfo, 122 }) 123 ads.ExpectNoResponse(t) 124 } 125 126 // Regression for envoy restart and overlapping connections 127 func TestAdsReconnect(t *testing.T) { 128 s := xdsfake.NewFakeDiscoveryServer(t, xdsfake.FakeOptions{}) 129 ads := s.ConnectADS().WithType(v3.ClusterType) 130 ads.RequestResponseAck(t, nil) 131 132 // envoy restarts and reconnects 133 ads2 := s.ConnectADS().WithType(v3.ClusterType) 134 ads2.RequestResponseAck(t, nil) 135 136 // closes old process 137 ads.Cleanup() 138 139 // event happens, expect push to the remaining connection 140 xds.AdsPushAll(s.Discovery) 141 ads2.ExpectResponse(t) 142 } 143 144 // Regression for connection with a bad ID 145 func TestAdsBadId(t *testing.T) { 146 s := xdsfake.NewFakeDiscoveryServer(t, xdsfake.FakeOptions{}) 147 ads := s.ConnectADS().WithID("").WithType(v3.ClusterType) 148 xds.AdsPushAll(s.Discovery) 149 ads.ExpectNoResponse(t) 150 } 151 152 func TestVersionNonce(t *testing.T) { 153 s := xdsfake.NewFakeDiscoveryServer(t, xdsfake.FakeOptions{}) 154 ads := s.ConnectADS().WithType(v3.ClusterType) 155 resp1 := ads.RequestResponseAck(t, nil) 156 fullPush(s) 157 resp2 := ads.ExpectResponse(t) 158 if !(resp1.VersionInfo < resp2.VersionInfo) { 159 t.Fatalf("version should be incrementing: %v -> %v", resp1.VersionInfo, resp2.VersionInfo) 160 } 161 if resp1.Nonce == resp2.Nonce { 162 t.Fatalf("nonce should change %v -> %v", resp1.Nonce, resp2.Nonce) 163 } 164 } 165 166 func TestAdsClusterUpdate(t *testing.T) { 167 s := xdsfake.NewFakeDiscoveryServer(t, xdsfake.FakeOptions{}) 168 ads := s.ConnectADS().WithType(v3.EndpointType) 169 170 version := "" 171 nonce := "" 172 sendEDSReqAndVerify := func(clusterName string) { 173 res := ads.RequestResponseAck(t, &discovery.DiscoveryRequest{ 174 ResourceNames: []string{clusterName}, 175 VersionInfo: version, 176 ResponseNonce: nonce, 177 }) 178 version = res.VersionInfo 179 nonce = res.Nonce 180 got := xdstest.MapKeys(xdstest.ExtractLoadAssignments(xdstest.UnmarshalClusterLoadAssignment(t, res.Resources))) 181 if len(got) != 1 { 182 t.Fatalf("expected 1 response, got %v", len(got)) 183 } 184 if got[0] != clusterName { 185 t.Fatalf("expected cluster %v got %v", clusterName, got[0]) 186 } 187 } 188 189 cluster1 := "outbound|80||local.default.svc.cluster.local" 190 sendEDSReqAndVerify(cluster1) 191 cluster2 := "outbound|80||hello.default.svc.cluster.local" 192 sendEDSReqAndVerify(cluster2) 193 } 194 195 // nolint: lll 196 func TestAdsPushScoping(t *testing.T) { 197 s := xdsfake.NewFakeDiscoveryServer(t, xdsfake.FakeOptions{}) 198 199 const ( 200 svcSuffix = ".testPushScoping.com" 201 ns1 = "ns1" 202 ) 203 204 removeServiceByNames := func(ns string, names ...string) { 205 configsUpdated := sets.New[model.ConfigKey]() 206 207 for _, name := range names { 208 hostname := host.Name(name) 209 s.MemRegistry.RemoveService(hostname) 210 configsUpdated.Insert(model.ConfigKey{ 211 Kind: kind.ServiceEntry, 212 Name: string(hostname), 213 Namespace: ns, 214 }) 215 } 216 217 s.Discovery.ConfigUpdate(&model.PushRequest{Full: true, ConfigsUpdated: configsUpdated}) 218 } 219 removeService := func(ns string, indexes ...int) { 220 var names []string 221 222 for _, i := range indexes { 223 names = append(names, fmt.Sprintf("svc%d%s", i, svcSuffix)) 224 } 225 226 removeServiceByNames(ns, names...) 227 } 228 addServiceByNames := func(ns string, names ...string) { 229 configsUpdated := sets.New[model.ConfigKey]() 230 231 for _, name := range names { 232 hostname := host.Name(name) 233 configsUpdated.Insert(model.ConfigKey{ 234 Kind: kind.ServiceEntry, 235 Name: string(hostname), 236 Namespace: ns, 237 }) 238 239 s.MemRegistry.AddService(&model.Service{ 240 Hostname: hostname, 241 DefaultAddress: "10.11.0.1", 242 Ports: []*model.Port{ 243 { 244 Name: "http-main", 245 Port: 2080, 246 Protocol: protocol.HTTP, 247 }, 248 }, 249 Attributes: model.ServiceAttributes{ 250 Namespace: ns, 251 }, 252 }) 253 } 254 255 s.Discovery.ConfigUpdate(&model.PushRequest{Full: true, ConfigsUpdated: configsUpdated}) 256 } 257 addService := func(ns string, indexes ...int) { 258 var hostnames []string 259 for _, i := range indexes { 260 hostnames = append(hostnames, fmt.Sprintf("svc%d%s", i, svcSuffix)) 261 } 262 addServiceByNames(ns, hostnames...) 263 } 264 265 addServiceInstance := func(hostname host.Name, indexes ...int) { 266 for _, i := range indexes { 267 s.MemRegistry.AddEndpoint(hostname, "http-main", 2080, "192.168.1.10", i) 268 } 269 270 s.Discovery.ConfigUpdate(&model.PushRequest{Full: false, ConfigsUpdated: sets.New(model.ConfigKey{Kind: kind.ServiceEntry, Name: string(hostname), Namespace: testConfigNamespace})}) 271 } 272 273 addVirtualService := func(i int, hosts []string, dest string) { 274 if _, err := s.Store().Create(config.Config{ 275 Meta: config.Meta{ 276 GroupVersionKind: gvk.VirtualService, 277 Name: fmt.Sprintf("vs%d", i), Namespace: testConfigNamespace, 278 }, 279 Spec: &networking.VirtualService{ 280 Hosts: hosts, 281 Http: []*networking.HTTPRoute{{ 282 Name: "dest-foo", 283 Route: []*networking.HTTPRouteDestination{{ 284 Destination: &networking.Destination{ 285 Host: dest, 286 }, 287 }}, 288 }}, 289 ExportTo: nil, 290 }, 291 }); err != nil { 292 t.Fatal(err) 293 } 294 } 295 removeVirtualService := func(i int) { 296 s.Store().Delete(gvk.VirtualService, fmt.Sprintf("vs%d", i), testConfigNamespace, nil) 297 } 298 299 addDelegateVirtualService := func(i int, hosts []string, dest string) { 300 if _, err := s.Store().Create(config.Config{ 301 Meta: config.Meta{ 302 GroupVersionKind: gvk.VirtualService, 303 Name: fmt.Sprintf("rootvs%d", i), Namespace: testConfigNamespace, 304 }, 305 Spec: &networking.VirtualService{ 306 Hosts: hosts, 307 308 Http: []*networking.HTTPRoute{{ 309 Name: "dest-foo", 310 Delegate: &networking.Delegate{ 311 Name: fmt.Sprintf("delegatevs%d", i), 312 Namespace: testConfigNamespace, 313 }, 314 }}, 315 ExportTo: nil, 316 }, 317 }); err != nil { 318 t.Fatal(err) 319 } 320 321 if _, err := s.Store().Create(config.Config{ 322 Meta: config.Meta{ 323 GroupVersionKind: gvk.VirtualService, 324 Name: fmt.Sprintf("delegatevs%d", i), Namespace: testConfigNamespace, 325 }, 326 Spec: &networking.VirtualService{ 327 Http: []*networking.HTTPRoute{{ 328 Name: "dest-foo", 329 Route: []*networking.HTTPRouteDestination{{ 330 Destination: &networking.Destination{ 331 Host: dest, 332 }, 333 }}, 334 }}, 335 ExportTo: nil, 336 }, 337 }); err != nil { 338 t.Fatal(err) 339 } 340 } 341 342 updateDelegateVirtualService := func(i int, dest string) { 343 if _, err := s.Store().Update(config.Config{ 344 Meta: config.Meta{ 345 GroupVersionKind: gvk.VirtualService, 346 Name: fmt.Sprintf("delegatevs%d", i), Namespace: testConfigNamespace, 347 }, 348 Spec: &networking.VirtualService{ 349 Http: []*networking.HTTPRoute{{ 350 Name: "dest-foo", 351 Headers: &networking.Headers{ 352 Request: &networking.Headers_HeaderOperations{ 353 Remove: []string{"any-string"}, 354 }, 355 }, 356 Route: []*networking.HTTPRouteDestination{ 357 { 358 Destination: &networking.Destination{ 359 Host: dest, 360 }, 361 }, 362 }, 363 }}, 364 ExportTo: nil, 365 }, 366 }); err != nil { 367 t.Fatal(err) 368 } 369 } 370 371 removeDelegateVirtualService := func(i int) { 372 s.Store().Delete(gvk.VirtualService, fmt.Sprintf("rootvs%d", i), testConfigNamespace, nil) 373 s.Store().Delete(gvk.VirtualService, fmt.Sprintf("delegatevs%d", i), testConfigNamespace, nil) 374 } 375 376 addDestinationRule := func(i int, host string) { 377 if _, err := s.Store().Create(config.Config{ 378 Meta: config.Meta{ 379 GroupVersionKind: gvk.DestinationRule, 380 Name: fmt.Sprintf("dr%d", i), Namespace: testConfigNamespace, 381 }, 382 Spec: &networking.DestinationRule{ 383 Host: host, 384 ExportTo: nil, 385 }, 386 }); err != nil { 387 t.Fatal(err) 388 } 389 } 390 removeDestinationRule := func(i int) { 391 s.Store().Delete(gvk.DestinationRule, fmt.Sprintf("dr%d", i), testConfigNamespace, nil) 392 } 393 394 sc := &networking.Sidecar{ 395 Egress: []*networking.IstioEgressListener{ 396 { 397 Hosts: []string{testConfigNamespace + "/*" + svcSuffix}, 398 }, 399 }, 400 } 401 scc := config.Config{ 402 Meta: config.Meta{ 403 GroupVersionKind: gvk.Sidecar, 404 Name: "sc", Namespace: testConfigNamespace, 405 }, 406 Spec: sc, 407 } 408 notMatchedScc := config.Config{ 409 Meta: config.Meta{ 410 GroupVersionKind: gvk.Sidecar, 411 Name: "notMatchedSc", Namespace: testConfigNamespace, 412 }, 413 Spec: &networking.Sidecar{ 414 WorkloadSelector: &networking.WorkloadSelector{ 415 Labels: map[string]string{"notMatched": "notMatched"}, 416 }, 417 }, 418 } 419 if _, err := s.Store().Create(scc); err != nil { 420 t.Fatal(err) 421 } 422 addService(testConfigNamespace, 1, 2, 3) 423 424 adscConn := s.Connect(nil, nil, nil) 425 defer adscConn.Close() 426 type svcCase struct { 427 desc string 428 429 ev model.Event 430 svcIndexes []int 431 svcNames []string 432 ns string 433 instIndexes []struct { 434 name string 435 indexes []int 436 } 437 vsIndexes []struct { 438 index int 439 hosts []string 440 dest string 441 } 442 delegatevsIndexes []struct { 443 index int 444 hosts []string 445 dest string 446 } 447 drIndexes []struct { 448 index int 449 host string 450 } 451 cfgs []config.Config 452 453 expectedUpdates []string 454 unexpectedUpdates []string 455 } 456 svcCases := []svcCase{ 457 { 458 desc: "Add a scoped service", 459 ev: model.EventAdd, 460 svcIndexes: []int{4}, 461 ns: testConfigNamespace, 462 expectedUpdates: []string{v3.ListenerType}, 463 }, // then: default 1,2,3,4 464 { 465 desc: "Add instances to a scoped service", 466 ev: model.EventAdd, 467 instIndexes: []struct { 468 name string 469 indexes []int 470 }{{fmt.Sprintf("svc%d%s", 4, svcSuffix), []int{1, 2}}}, 471 ns: testConfigNamespace, 472 expectedUpdates: []string{v3.EndpointType}, 473 }, // then: default 1,2,3,4 474 { 475 desc: "Add virtual service to a scoped service", 476 ev: model.EventAdd, 477 vsIndexes: []struct { 478 index int 479 hosts []string 480 dest string 481 }{{index: 4, hosts: []string{fmt.Sprintf("svc%d%s", 4, svcSuffix)}, dest: "unknown-svc"}}, 482 expectedUpdates: []string{v3.ListenerType}, 483 }, 484 { 485 desc: "Delete virtual service of a scoped service", 486 ev: model.EventDelete, 487 vsIndexes: []struct { 488 index int 489 hosts []string 490 dest string 491 }{{index: 4}}, 492 expectedUpdates: []string{v3.ListenerType}, 493 }, 494 { 495 desc: "Add destination rule to a scoped service", 496 ev: model.EventAdd, 497 drIndexes: []struct { 498 index int 499 host string 500 }{{4, fmt.Sprintf("svc%d%s", 4, svcSuffix)}}, 501 expectedUpdates: []string{v3.ClusterType}, 502 }, 503 { 504 desc: "Delete destination rule of a scoped service", 505 ev: model.EventDelete, 506 drIndexes: []struct { 507 index int 508 host string 509 }{{index: 4}}, 510 expectedUpdates: []string{v3.ClusterType}, 511 }, 512 { 513 desc: "Add a unscoped(name not match) service", 514 ev: model.EventAdd, 515 svcNames: []string{"foo.com"}, 516 ns: testConfigNamespace, 517 unexpectedUpdates: []string{v3.ClusterType}, 518 }, // then: default 1,2,3,4, foo.com; ns1: 11 519 { 520 desc: "Add instances to an unscoped service", 521 ev: model.EventAdd, 522 instIndexes: []struct { 523 name string 524 indexes []int 525 }{{"foo.com", []int{1, 2}}}, 526 ns: testConfigNamespace, 527 unexpectedUpdates: []string{v3.EndpointType}, 528 }, // then: default 1,2,3,4 529 { 530 desc: "Add a unscoped(ns not match) service", 531 ev: model.EventAdd, 532 svcIndexes: []int{11}, 533 ns: ns1, 534 unexpectedUpdates: []string{v3.ClusterType}, 535 }, // then: default 1,2,3,4, foo.com; ns1: 11 536 { 537 desc: "Add virtual service to an unscoped service", 538 ev: model.EventAdd, 539 vsIndexes: []struct { 540 index int 541 hosts []string 542 dest string 543 }{{index: 0, hosts: []string{"foo.com"}, dest: "unknown-service"}}, 544 unexpectedUpdates: []string{v3.ClusterType}, 545 }, 546 { 547 desc: "Delete virtual service of a unscoped service", 548 ev: model.EventDelete, 549 vsIndexes: []struct { 550 index int 551 hosts []string 552 dest string 553 }{{index: 0}}, 554 unexpectedUpdates: []string{v3.ClusterType}, 555 }, 556 { 557 desc: "Add destination rule to an unscoped service", 558 ev: model.EventAdd, 559 drIndexes: []struct { 560 index int 561 host string 562 }{{0, "foo.com"}}, 563 unexpectedUpdates: []string{v3.ClusterType}, 564 }, 565 { 566 desc: "Delete destination rule of a unscoped service", 567 ev: model.EventDelete, 568 drIndexes: []struct { 569 index int 570 host string 571 }{{index: 0}}, 572 unexpectedUpdates: []string{v3.ClusterType}, 573 }, 574 { 575 desc: "Add virtual service for scoped service with transitively scoped dest svc", 576 ev: model.EventAdd, 577 vsIndexes: []struct { 578 index int 579 hosts []string 580 dest string 581 }{{index: 4, hosts: []string{fmt.Sprintf("svc%d%s", 4, svcSuffix)}, dest: "foo.com"}}, 582 expectedUpdates: []string{v3.ClusterType, v3.EndpointType}, 583 }, 584 { 585 desc: "Add instances for transitively scoped svc", 586 ev: model.EventAdd, 587 instIndexes: []struct { 588 name string 589 indexes []int 590 }{{"foo.com", []int{1, 2}}}, 591 ns: testConfigNamespace, 592 expectedUpdates: []string{v3.EndpointType}, 593 }, 594 { 595 desc: "Delete virtual service for scoped service with transitively scoped dest svc", 596 ev: model.EventDelete, 597 vsIndexes: []struct { 598 index int 599 hosts []string 600 dest string 601 }{{index: 4}}, 602 expectedUpdates: []string{v3.ClusterType}, 603 }, 604 { 605 desc: "Add delegation virtual service for scoped service with transitively scoped dest svc", 606 ev: model.EventAdd, 607 delegatevsIndexes: []struct { 608 index int 609 hosts []string 610 dest string 611 }{{index: 4, hosts: []string{fmt.Sprintf("svc%d%s", 4, svcSuffix)}, dest: "foo.com"}}, 612 expectedUpdates: []string{v3.ListenerType, v3.RouteType, v3.ClusterType, v3.EndpointType}, 613 }, 614 { 615 desc: "Update delegate virtual service should trigger full push", 616 ev: model.EventUpdate, 617 delegatevsIndexes: []struct { 618 index int 619 hosts []string 620 dest string 621 }{{index: 4, hosts: []string{fmt.Sprintf("svc%d%s", 4, svcSuffix)}, dest: "foo.com"}}, 622 expectedUpdates: []string{v3.ListenerType, v3.RouteType, v3.ClusterType}, 623 }, 624 { 625 desc: "Delete delegate virtual service for scoped service with transitively scoped dest svc", 626 ev: model.EventDelete, 627 delegatevsIndexes: []struct { 628 index int 629 hosts []string 630 dest string 631 }{{index: 4}}, 632 expectedUpdates: []string{v3.ListenerType, v3.RouteType, v3.ClusterType}, 633 }, 634 { 635 desc: "Remove a scoped service", 636 ev: model.EventDelete, 637 svcIndexes: []int{4}, 638 ns: testConfigNamespace, 639 expectedUpdates: []string{v3.ListenerType}, 640 }, // then: default 1,2,3, foo.com; ns: 11 641 { 642 desc: "Remove a unscoped(name not match) service", 643 ev: model.EventDelete, 644 svcNames: []string{"foo.com"}, 645 ns: testConfigNamespace, 646 unexpectedUpdates: []string{v3.ClusterType}, 647 }, // then: default 1,2,3; ns1: 11 648 { 649 desc: "Remove a unscoped(ns not match) service", 650 ev: model.EventDelete, 651 svcIndexes: []int{11}, 652 ns: ns1, 653 unexpectedUpdates: []string{v3.ClusterType}, 654 }, // then: default 1,2,3 655 { 656 desc: "Add an unmatched Sidecar config", 657 ev: model.EventAdd, 658 cfgs: []config.Config{notMatchedScc}, 659 ns: testConfigNamespace, 660 unexpectedUpdates: []string{v3.ListenerType, v3.RouteType, v3.ClusterType, v3.EndpointType}, 661 }, 662 { 663 desc: "Update the Sidecar config", 664 ev: model.EventUpdate, 665 cfgs: []config.Config{scc}, 666 ns: testConfigNamespace, 667 expectedUpdates: []string{v3.ListenerType, v3.RouteType, v3.ClusterType, v3.EndpointType}, 668 }, 669 } 670 671 for _, c := range svcCases { 672 t.Run(c.desc, func(t *testing.T) { 673 // Let events from previous tests complete 674 time.Sleep(time.Millisecond * 50) 675 adscConn.WaitClear() 676 var wantUpdates []string 677 wantUpdates = append(wantUpdates, c.expectedUpdates...) 678 wantUpdates = append(wantUpdates, c.unexpectedUpdates...) 679 680 switch c.ev { 681 case model.EventAdd: 682 if len(c.svcIndexes) > 0 { 683 addService(c.ns, c.svcIndexes...) 684 } 685 if len(c.svcNames) > 0 { 686 addServiceByNames(c.ns, c.svcNames...) 687 } 688 if len(c.instIndexes) > 0 { 689 for _, instIndex := range c.instIndexes { 690 addServiceInstance(host.Name(instIndex.name), instIndex.indexes...) 691 } 692 } 693 if len(c.vsIndexes) > 0 { 694 for _, vsIndex := range c.vsIndexes { 695 addVirtualService(vsIndex.index, vsIndex.hosts, vsIndex.dest) 696 } 697 } 698 if len(c.delegatevsIndexes) > 0 { 699 for _, vsIndex := range c.delegatevsIndexes { 700 addDelegateVirtualService(vsIndex.index, vsIndex.hosts, vsIndex.dest) 701 } 702 } 703 if len(c.drIndexes) > 0 { 704 for _, drIndex := range c.drIndexes { 705 addDestinationRule(drIndex.index, drIndex.host) 706 } 707 } 708 if len(c.cfgs) > 0 { 709 for _, cfg := range c.cfgs { 710 if _, err := s.Store().Create(cfg); err != nil { 711 t.Fatal(err) 712 } 713 } 714 } 715 case model.EventUpdate: 716 if len(c.delegatevsIndexes) > 0 { 717 for _, vsIndex := range c.delegatevsIndexes { 718 updateDelegateVirtualService(vsIndex.index, vsIndex.dest) 719 } 720 } 721 if len(c.cfgs) > 0 { 722 for _, cfg := range c.cfgs { 723 if _, err := s.Store().Update(cfg); err != nil { 724 t.Fatal(err) 725 } 726 } 727 } 728 case model.EventDelete: 729 if len(c.svcIndexes) > 0 { 730 removeService(c.ns, c.svcIndexes...) 731 } 732 if len(c.svcNames) > 0 { 733 removeServiceByNames(c.ns, c.svcNames...) 734 } 735 if len(c.vsIndexes) > 0 { 736 for _, vsIndex := range c.vsIndexes { 737 removeVirtualService(vsIndex.index) 738 } 739 } 740 if len(c.delegatevsIndexes) > 0 { 741 for _, vsIndex := range c.delegatevsIndexes { 742 removeDelegateVirtualService(vsIndex.index) 743 } 744 } 745 if len(c.drIndexes) > 0 { 746 for _, drIndex := range c.drIndexes { 747 removeDestinationRule(drIndex.index) 748 } 749 } 750 default: 751 t.Fatalf("wrong event for case %v", c) 752 } 753 754 timeout := time.Millisecond * 200 755 upd, _ := adscConn.Wait(timeout, wantUpdates...) 756 for _, expect := range c.expectedUpdates { 757 if !slices.Contains(upd, expect) { 758 t.Fatalf("expected update %s not in updates %v", expect, upd) 759 } 760 } 761 for _, unexpect := range c.unexpectedUpdates { 762 if slices.Contains(upd, unexpect) { 763 t.Fatalf("expected to not get update %s, but it is in updates %v", unexpect, upd) 764 } 765 } 766 }) 767 } 768 } 769 770 func TestAdsUpdate(t *testing.T) { 771 s := xdsfake.NewFakeDiscoveryServer(t, xdsfake.FakeOptions{}) 772 ads := s.ConnectADS() 773 774 s.MemRegistry.AddService(&model.Service{ 775 Hostname: "adsupdate.default.svc.cluster.local", 776 DefaultAddress: "10.11.0.1", 777 Ports: []*model.Port{ 778 { 779 Name: "http-main", 780 Port: 2080, 781 Protocol: protocol.HTTP, 782 }, 783 }, 784 Attributes: model.ServiceAttributes{ 785 Name: "adsupdate", 786 Namespace: "default", 787 }, 788 }) 789 s.Discovery.ConfigUpdate(&model.PushRequest{Full: true}) 790 time.Sleep(time.Millisecond * 200) 791 s.MemRegistry.SetEndpoints("adsupdate.default.svc.cluster.local", "default", 792 newEndpointWithAccount("10.2.0.1", "hello-sa", "v1")) 793 794 cluster := "outbound|2080||adsupdate.default.svc.cluster.local" 795 res := ads.RequestResponseAck(t, &discovery.DiscoveryRequest{ 796 ResourceNames: []string{cluster}, 797 TypeUrl: v3.EndpointType, 798 }) 799 eps, f := xdstest.ExtractLoadAssignments(xdstest.UnmarshalClusterLoadAssignment(t, res.GetResources()))[cluster] 800 if !f { 801 t.Fatalf("did not find cluster %v", cluster) 802 } 803 if !reflect.DeepEqual(eps, []string{"10.2.0.1:80"}) { 804 t.Fatalf("expected endpoints [10.2.0.1:80] got %v", eps) 805 } 806 807 _ = s.MemRegistry.AddEndpoint("adsupdate.default.svc.cluster.local", 808 "http-main", 2080, "10.1.7.1", 1080) 809 810 // will trigger recompute and push for all clients - including some that may be closing 811 // This reproduced the 'push on closed connection' bug. 812 xds.AdsPushAll(s.Discovery) 813 res1 := ads.ExpectResponse(t) 814 xdstest.UnmarshalClusterLoadAssignment(t, res1.GetResources()) 815 } 816 817 func TestEnvoyRDSProtocolError(t *testing.T) { 818 s := xdsfake.NewFakeDiscoveryServer(t, xdsfake.FakeOptions{}) 819 ads := s.ConnectADS().WithType(v3.RouteType) 820 ads.RequestResponseAck(t, &discovery.DiscoveryRequest{ResourceNames: []string{routeA}}) 821 822 xds.AdsPushAll(s.Discovery) 823 res := ads.ExpectResponse(t) 824 825 // send empty response and validate no response is returned. 826 ads.Request(t, &discovery.DiscoveryRequest{ 827 ResourceNames: nil, 828 VersionInfo: res.VersionInfo, 829 ResponseNonce: res.Nonce, 830 }) 831 ads.ExpectNoResponse(t) 832 833 // Refresh routes 834 ads.Request(t, &discovery.DiscoveryRequest{ 835 ResourceNames: []string{routeA, routeB}, 836 VersionInfo: res.VersionInfo, 837 ResponseNonce: res.Nonce, 838 }) 839 } 840 841 func TestEnvoyRDSUpdatedRouteRequest(t *testing.T) { 842 expectRoutes := func(resp *discovery.DiscoveryResponse, expected ...string) { 843 t.Helper() 844 got := xdstest.MapKeys(xdstest.ExtractRouteConfigurations(xdstest.UnmarshalRouteConfiguration(t, resp.Resources))) 845 if !reflect.DeepEqual(expected, got) { 846 t.Fatalf("expected routes %v got %v", expected, got) 847 } 848 } 849 s := xdsfake.NewFakeDiscoveryServer(t, xdsfake.FakeOptions{}) 850 ads := s.ConnectADS().WithType(v3.RouteType) 851 resp := ads.RequestResponseAck(t, &discovery.DiscoveryRequest{ResourceNames: []string{routeA}}) 852 expectRoutes(resp, routeA) 853 854 xds.AdsPushAll(s.Discovery) 855 resp = ads.ExpectResponse(t) 856 expectRoutes(resp, routeA) 857 858 // Test update from A -> B 859 resp = ads.RequestResponseAck(t, &discovery.DiscoveryRequest{ResourceNames: []string{routeB}}) 860 expectRoutes(resp, routeB) 861 862 // Test update from B -> A, B 863 resp = ads.RequestResponseAck(t, &discovery.DiscoveryRequest{ResourceNames: []string{routeA, routeB}}) 864 expectRoutes(resp, routeA, routeB) 865 866 // Test update from B, B -> A 867 resp = ads.RequestResponseAck(t, &discovery.DiscoveryRequest{ResourceNames: []string{routeA}}) 868 expectRoutes(resp, routeA) 869 } 870 871 func TestEdsCache(t *testing.T) { 872 makeEndpoint := func(addr []*networking.WorkloadEntry) config.Config { 873 return config.Config{ 874 Meta: config.Meta{ 875 Name: "service", 876 Namespace: "default", 877 GroupVersionKind: gvk.ServiceEntry, 878 }, 879 Spec: &networking.ServiceEntry{ 880 Hosts: []string{"foo.com"}, 881 Ports: []*networking.ServicePort{{ 882 Number: 80, 883 Protocol: "HTTP", 884 Name: "http", 885 }}, 886 Resolution: networking.ServiceEntry_STATIC, 887 Endpoints: addr, 888 }, 889 } 890 } 891 assertEndpoints := func(a *adsc.ADSC, addr ...string) { 892 t.Helper() 893 retry.UntilSuccessOrFail(t, func() error { 894 got := sets.New(xdstest.ExtractEndpoints(a.GetEndpoints()["outbound|80||foo.com"])...) 895 want := sets.New(addr...) 896 897 if !got.Equals(want) { 898 return fmt.Errorf("invalid endpoints, got %v want %v", got, addr) 899 } 900 return nil 901 }, retry.Timeout(time.Second*5)) 902 } 903 904 s := xdsfake.NewFakeDiscoveryServer(t, xdsfake.FakeOptions{ 905 Configs: []config.Config{ 906 makeEndpoint([]*networking.WorkloadEntry{ 907 {Address: "1.2.3.4", Locality: "region/zone"}, 908 {Address: "1.2.3.5", Locality: "notmatch"}, 909 }), 910 }, 911 }) 912 ads := s.Connect(&model.Proxy{Locality: &core.Locality{Region: "region"}}, nil, watchAll) 913 914 assertEndpoints(ads, "1.2.3.4:80", "1.2.3.5:80") 915 t.Logf("endpoints: %+v", xdstest.ExtractEndpoints(ads.GetEndpoints()["outbound|80||foo.com"])) 916 917 if _, err := s.Store().Update(makeEndpoint([]*networking.WorkloadEntry{ 918 {Address: "1.2.3.6", Locality: "region/zone"}, 919 {Address: "1.2.3.5", Locality: "notmatch"}, 920 })); err != nil { 921 t.Fatal(err) 922 } 923 if _, err := ads.Wait(time.Second*5, v3.EndpointType); err != nil { 924 t.Fatal(err) 925 } 926 assertEndpoints(ads, "1.2.3.6:80", "1.2.3.5:80") 927 t.Logf("endpoints: %+v", xdstest.ExtractEndpoints(ads.GetEndpoints()["outbound|80||foo.com"])) 928 929 ads.WaitClear() 930 if _, err := s.Store().Create(config.Config{ 931 Meta: config.Meta{ 932 Name: "service", 933 Namespace: "default", 934 GroupVersionKind: gvk.DestinationRule, 935 }, 936 Spec: &networking.DestinationRule{ 937 Host: "foo.com", 938 TrafficPolicy: &networking.TrafficPolicy{ 939 OutlierDetection: &networking.OutlierDetection{}, 940 }, 941 }, 942 }); err != nil { 943 t.Fatal(err) 944 } 945 if _, err := ads.Wait(time.Second*5, v3.EndpointType); err != nil { 946 t.Fatal(err) 947 } 948 assertEndpoints(ads, "1.2.3.6:80", "1.2.3.5:80") 949 retry.UntilSuccessOrFail(t, func() error { 950 found := false 951 for _, ep := range ads.GetEndpoints()["outbound|80||foo.com"].Endpoints { 952 if ep.Priority == 1 { 953 found = true 954 } 955 } 956 if !found { 957 return fmt.Errorf("locality did not update") 958 } 959 return nil 960 }, retry.Timeout(time.Second*5)) 961 962 ads.WaitClear() 963 964 ep := makeEndpoint([]*networking.WorkloadEntry{{Address: "1.2.3.6", Locality: "region/zone"}, {Address: "1.2.3.5", Locality: "notmatch"}}) 965 ep.Spec.(*networking.ServiceEntry).Resolution = networking.ServiceEntry_DNS 966 if _, err := s.Store().Update(ep); err != nil { 967 t.Fatal(err) 968 } 969 if _, err := ads.Wait(time.Second*5, v3.EndpointType); err != nil { 970 t.Fatal(err) 971 } 972 assertEndpoints(ads) 973 t.Logf("endpoints: %+v", ads.GetEndpoints()) 974 } 975 976 // TestPushQueueLeak is a regression test for https://github.com/grpc/grpc-go/issues/4758 977 func TestPushQueueLeak(t *testing.T) { 978 ds := xdsfake.NewFakeDiscoveryServer(t, xdsfake.FakeOptions{}) 979 p := ds.ConnectADS() 980 p.RequestResponseAck(t, nil) 981 for _, c := range ds.Discovery.AllClients() { 982 leak.MustGarbageCollect(t, c) 983 } 984 ds.Discovery.AdsPushAll(&model.PushRequest{Push: ds.PushContext()}) 985 p.Cleanup() 986 } 987 988 func TestDistribution(t *testing.T) { 989 xds.ResetConnectionNumberForTest() 990 s := xdsfake.NewFakeDiscoveryServer(t, xdsfake.FakeOptions{}) 991 expectNonce := func(nonce, ty string) { 992 t.Helper() 993 assert.EventuallyEqual(t, func() string { 994 return s.Discovery.StatusReporter.QueryLastNonce("test.default-1", ty) 995 }, nonce[:xds.VersionLen]) 996 } 997 998 ledger := ledger.Make(time.Minute) 999 ledger.Put("key", "value") // If there is no config, ledger would be empty 1000 s.Env().SetLedger(ledger) 1001 reporter := &distribution.Reporter{ 1002 UpdateInterval: features.StatusUpdateInterval, 1003 } 1004 reporter.Init(s.Env().GetLedger(), test.NewStop(t)) 1005 s.Discovery.StatusReporter = reporter 1006 1007 ads := s.ConnectADS().WithType(v3.ClusterType) 1008 // Subscribe to clusters 1009 res1 := ads.RequestResponseAck(t, &discovery.DiscoveryRequest{ResourceNames: []string{"fake-cluster"}}) 1010 expectNonce(res1.Nonce, v3.ClusterType) 1011 1012 // Send a push 1013 s.Discovery.Push(&model.PushRequest{Full: true, Push: s.Env().PushContext()}) 1014 res := ads.ExpectResponse(t) 1015 // Not yet ACKed, should return last one 1016 expectNonce(res1.Nonce, v3.ClusterType) 1017 expectNonce(res.Nonce, v3.RouteType) 1018 1019 ads.Request(t, &discovery.DiscoveryRequest{ 1020 VersionInfo: res.VersionInfo, 1021 ResourceNames: []string{"fake-cluster"}, 1022 TypeUrl: v3.ClusterType, 1023 ResponseNonce: res.Nonce, 1024 }) 1025 // After ACK, should be updated 1026 expectNonce(res.Nonce, v3.ClusterType) 1027 // Types we are not subscribed to are also updated 1028 expectNonce(res.Nonce, v3.RouteType) 1029 // Ledger has no explicit close, only through GC, so we need to make sure it can be GCed 1030 s.Env().SetLedger(nil) 1031 s.Discovery.StatusReporter = nil 1032 }