github.com/hxx258456/ccgo@v0.0.5-0.20230213014102-48b35f46f66f/grpc/xds/internal/resolver/xds_resolver_test.go (about) 1 /* 2 * 3 * Copyright 2019 gRPC authors. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 */ 18 19 package resolver 20 21 import ( 22 "context" 23 "errors" 24 "net/url" 25 "reflect" 26 "strings" 27 "testing" 28 "time" 29 30 xxhash "github.com/cespare/xxhash/v2" 31 "github.com/google/go-cmp/cmp" 32 "github.com/hxx258456/ccgo/grpc/codes" 33 "github.com/hxx258456/ccgo/grpc/credentials/insecure" 34 xdscreds "github.com/hxx258456/ccgo/grpc/credentials/xds" 35 "github.com/hxx258456/ccgo/grpc/internal" 36 "github.com/hxx258456/ccgo/grpc/internal/envconfig" 37 "github.com/hxx258456/ccgo/grpc/internal/grpcrand" 38 "github.com/hxx258456/ccgo/grpc/internal/grpctest" 39 iresolver "github.com/hxx258456/ccgo/grpc/internal/resolver" 40 "github.com/hxx258456/ccgo/grpc/internal/testutils" 41 "github.com/hxx258456/ccgo/grpc/internal/wrr" 42 "github.com/hxx258456/ccgo/grpc/metadata" 43 "github.com/hxx258456/ccgo/grpc/resolver" 44 "github.com/hxx258456/ccgo/grpc/serviceconfig" 45 "github.com/hxx258456/ccgo/grpc/status" 46 _ "github.com/hxx258456/ccgo/grpc/xds/internal/balancer/cdsbalancer" // To parse LB config 47 "github.com/hxx258456/ccgo/grpc/xds/internal/balancer/clustermanager" 48 "github.com/hxx258456/ccgo/grpc/xds/internal/balancer/ringhash" 49 "github.com/hxx258456/ccgo/grpc/xds/internal/httpfilter" 50 "github.com/hxx258456/ccgo/grpc/xds/internal/httpfilter/router" 51 "github.com/hxx258456/ccgo/grpc/xds/internal/testutils/fakeclient" 52 "github.com/hxx258456/ccgo/grpc/xds/internal/xdsclient" 53 "github.com/hxx258456/ccgo/grpc/xds/internal/xdsclient/bootstrap" 54 "github.com/hxx258456/ccgo/grpc/xds/internal/xdsclient/xdsresource" 55 ) 56 57 const ( 58 targetStr = "target" 59 routeStr = "route" 60 cluster = "cluster" 61 defaultTestTimeout = 1 * time.Second 62 defaultTestShortTimeout = 100 * time.Microsecond 63 ) 64 65 var target = resolver.Target{Endpoint: targetStr, URL: url.URL{Scheme: "xds", Path: "/" + targetStr}} 66 67 var routerFilter = xdsresource.HTTPFilter{Name: "rtr", Filter: httpfilter.Get(router.TypeURL)} 68 var routerFilterList = []xdsresource.HTTPFilter{routerFilter} 69 70 type s struct { 71 grpctest.Tester 72 } 73 74 func Test(t *testing.T) { 75 grpctest.RunSubTests(t, s{}) 76 } 77 78 func (s) TestRegister(t *testing.T) { 79 b := resolver.Get(xdsScheme) 80 if b == nil { 81 t.Errorf("scheme %v is not registered", xdsScheme) 82 } 83 } 84 85 // testClientConn is a fake implemetation of resolver.ClientConn. All is does 86 // is to store the state received from the resolver locally and signal that 87 // event through a channel. 88 type testClientConn struct { 89 resolver.ClientConn 90 stateCh *testutils.Channel 91 errorCh *testutils.Channel 92 } 93 94 func (t *testClientConn) UpdateState(s resolver.State) error { 95 t.stateCh.Send(s) 96 return nil 97 } 98 99 func (t *testClientConn) ReportError(err error) { 100 t.errorCh.Send(err) 101 } 102 103 func (t *testClientConn) ParseServiceConfig(jsonSC string) *serviceconfig.ParseResult { 104 return internal.ParseServiceConfigForTesting.(func(string) *serviceconfig.ParseResult)(jsonSC) 105 } 106 107 func newTestClientConn() *testClientConn { 108 return &testClientConn{ 109 stateCh: testutils.NewChannel(), 110 errorCh: testutils.NewChannel(), 111 } 112 } 113 114 // TestResolverBuilder tests the xdsResolverBuilder's Build method with 115 // different parameters. 116 func (s) TestResolverBuilder(t *testing.T) { 117 tests := []struct { 118 name string 119 xdsClientFunc func() (xdsclient.XDSClient, error) 120 target resolver.Target 121 wantErr bool 122 }{ 123 { 124 name: "simple-good", 125 xdsClientFunc: func() (xdsclient.XDSClient, error) { 126 return fakeclient.NewClient(), nil 127 }, 128 target: target, 129 wantErr: false, 130 }, 131 { 132 name: "newXDSClient-throws-error", 133 xdsClientFunc: func() (xdsclient.XDSClient, error) { 134 return nil, errors.New("newXDSClient-throws-error") 135 }, 136 target: target, 137 wantErr: true, 138 }, 139 { 140 name: "authority not defined in bootstrap", 141 xdsClientFunc: func() (xdsclient.XDSClient, error) { 142 c := fakeclient.NewClient() 143 c.SetBootstrapConfig(&bootstrap.Config{ 144 ClientDefaultListenerResourceNameTemplate: "%s", 145 Authorities: map[string]*bootstrap.Authority{ 146 "test-authority": { 147 ClientListenerResourceNameTemplate: "xdstp://test-authority/%s", 148 }, 149 }, 150 }) 151 return c, nil 152 }, 153 target: resolver.Target{ 154 URL: url.URL{ 155 Host: "non-existing-authority", 156 Path: "/" + targetStr, 157 }, 158 }, 159 wantErr: true, 160 }, 161 } 162 for _, test := range tests { 163 t.Run(test.name, func(t *testing.T) { 164 // Fake out the xdsClient creation process by providing a fake. 165 oldClientMaker := newXDSClient 166 newXDSClient = test.xdsClientFunc 167 defer func() { 168 newXDSClient = oldClientMaker 169 }() 170 171 builder := resolver.Get(xdsScheme) 172 if builder == nil { 173 t.Fatalf("resolver.Get(%v) returned nil", xdsScheme) 174 } 175 176 r, err := builder.Build(test.target, newTestClientConn(), resolver.BuildOptions{}) 177 if (err != nil) != test.wantErr { 178 t.Fatalf("builder.Build(%v) returned err: %v, wantErr: %v", target, err, test.wantErr) 179 } 180 if err != nil { 181 // This is the case where we expect an error and got it. 182 return 183 } 184 r.Close() 185 }) 186 } 187 } 188 189 // TestResolverBuilder_xdsCredsBootstrapMismatch tests the case where an xds 190 // resolver is built with xds credentials being specified by the user. The 191 // bootstrap file does not contain any certificate provider configuration 192 // though, and therefore we expect the resolver build to fail. 193 func (s) TestResolverBuilder_xdsCredsBootstrapMismatch(t *testing.T) { 194 // Fake out the xdsClient creation process by providing a fake, which does 195 // not have any certificate provider configuration. 196 fc := fakeclient.NewClient() 197 fc.SetBootstrapConfig(&bootstrap.Config{}) 198 oldClientMaker := newXDSClient 199 newXDSClient = func() (xdsclient.XDSClient, error) { 200 return fc, nil 201 } 202 defer func() { newXDSClient = oldClientMaker }() 203 defer func() { 204 select { 205 case <-time.After(defaultTestTimeout): 206 t.Fatalf("timeout waiting for close") 207 case <-fc.Closed.Done(): 208 } 209 }() 210 211 builder := resolver.Get(xdsScheme) 212 if builder == nil { 213 t.Fatalf("resolver.Get(%v) returned nil", xdsScheme) 214 } 215 216 // Create xds credentials to be passed to resolver.Build(). 217 creds, err := xdscreds.NewClientCredentials(xdscreds.ClientOptions{FallbackCreds: insecure.NewCredentials()}) 218 if err != nil { 219 t.Fatalf("xds.NewClientCredentials() failed: %v", err) 220 } 221 222 // Since the fake xds client is not configured with any certificate provider 223 // configs, and we are specifying xds credentials in the call to 224 // resolver.Build(), we expect it to fail. 225 if _, err := builder.Build(target, newTestClientConn(), resolver.BuildOptions{DialCreds: creds}); err == nil { 226 t.Fatal("builder.Build() succeeded when expected to fail") 227 } 228 } 229 230 type setupOpts struct { 231 bootstrapC *bootstrap.Config 232 target resolver.Target 233 } 234 235 func testSetup(t *testing.T, opts setupOpts) (*xdsResolver, *fakeclient.Client, *testClientConn, func()) { 236 t.Helper() 237 238 fc := fakeclient.NewClient() 239 if opts.bootstrapC != nil { 240 fc.SetBootstrapConfig(opts.bootstrapC) 241 } 242 oldClientMaker := newXDSClient 243 newXDSClient = func() (xdsclient.XDSClient, error) { 244 return fc, nil 245 } 246 cancel := func() { 247 // Make sure the xDS client is closed, in all (successful or failed) 248 // cases. 249 select { 250 case <-time.After(defaultTestTimeout): 251 t.Fatalf("timeout waiting for close") 252 case <-fc.Closed.Done(): 253 } 254 newXDSClient = oldClientMaker 255 } 256 builder := resolver.Get(xdsScheme) 257 if builder == nil { 258 t.Fatalf("resolver.Get(%v) returned nil", xdsScheme) 259 } 260 261 tcc := newTestClientConn() 262 r, err := builder.Build(opts.target, tcc, resolver.BuildOptions{}) 263 if err != nil { 264 t.Fatalf("builder.Build(%v) returned err: %v", target, err) 265 } 266 return r.(*xdsResolver), fc, tcc, func() { 267 r.Close() 268 cancel() 269 } 270 } 271 272 // waitForWatchListener waits for the WatchListener method to be called on the 273 // xdsClient within a reasonable amount of time, and also verifies that the 274 // watch is called with the expected target. 275 func waitForWatchListener(ctx context.Context, t *testing.T, xdsC *fakeclient.Client, wantTarget string) { 276 t.Helper() 277 278 gotTarget, err := xdsC.WaitForWatchListener(ctx) 279 if err != nil { 280 t.Fatalf("xdsClient.WatchService failed with error: %v", err) 281 } 282 if gotTarget != wantTarget { 283 t.Fatalf("xdsClient.WatchService() called with target: %v, want %v", gotTarget, wantTarget) 284 } 285 } 286 287 // waitForWatchRouteConfig waits for the WatchRoute method to be called on the 288 // xdsClient within a reasonable amount of time, and also verifies that the 289 // watch is called with the expected target. 290 func waitForWatchRouteConfig(ctx context.Context, t *testing.T, xdsC *fakeclient.Client, wantTarget string) { 291 t.Helper() 292 293 gotTarget, err := xdsC.WaitForWatchRouteConfig(ctx) 294 if err != nil { 295 t.Fatalf("xdsClient.WatchService failed with error: %v", err) 296 } 297 if gotTarget != wantTarget { 298 t.Fatalf("xdsClient.WatchService() called with target: %v, want %v", gotTarget, wantTarget) 299 } 300 } 301 302 // TestXDSResolverResourceNameToWatch tests that the correct resource name is 303 // used to watch for the service. This covers cases with different bootstrap 304 // config, and different authority. 305 func (s) TestXDSResolverResourceNameToWatch(t *testing.T) { 306 tests := []struct { 307 name string 308 bc *bootstrap.Config 309 target resolver.Target 310 want string 311 }{ 312 { 313 name: "default %s old style", 314 bc: &bootstrap.Config{ 315 ClientDefaultListenerResourceNameTemplate: "%s", 316 }, 317 target: resolver.Target{ 318 URL: url.URL{Path: "/" + targetStr}, 319 }, 320 want: targetStr, 321 }, 322 { 323 name: "old style no percent encoding", 324 bc: &bootstrap.Config{ 325 ClientDefaultListenerResourceNameTemplate: "/path/to/%s", 326 }, 327 target: resolver.Target{ 328 URL: url.URL{Path: "/" + targetStr}, 329 }, 330 want: "/path/to/" + targetStr, 331 }, 332 { 333 name: "new style with %s", 334 bc: &bootstrap.Config{ 335 ClientDefaultListenerResourceNameTemplate: "xdstp://authority.com/%s", 336 Authorities: nil, 337 }, 338 target: resolver.Target{ 339 URL: url.URL{Path: "/0.0.0.0:8080"}, 340 }, 341 want: "xdstp://authority.com/0.0.0.0:8080", 342 }, 343 { 344 name: "new style percent encoding", 345 bc: &bootstrap.Config{ 346 ClientDefaultListenerResourceNameTemplate: "xdstp://authority.com/%s", 347 Authorities: nil, 348 }, 349 target: resolver.Target{ 350 URL: url.URL{Path: "/[::1]:8080"}, 351 }, 352 want: "xdstp://authority.com/%5B::1%5D:8080", 353 }, 354 { 355 name: "new style different authority", 356 bc: &bootstrap.Config{ 357 ClientDefaultListenerResourceNameTemplate: "xdstp://authority.com/%s", 358 Authorities: map[string]*bootstrap.Authority{ 359 "test-authority": { 360 ClientListenerResourceNameTemplate: "xdstp://test-authority/%s", 361 }, 362 }, 363 }, 364 target: resolver.Target{ 365 URL: url.URL{ 366 Host: "test-authority", 367 Path: "/" + targetStr, 368 }, 369 }, 370 want: "xdstp://test-authority/" + targetStr, 371 }, 372 } 373 for _, tt := range tests { 374 t.Run(tt.name, func(t *testing.T) { 375 xdsR, xdsC, _, cancel := testSetup(t, setupOpts{ 376 bootstrapC: tt.bc, 377 target: tt.target, 378 }) 379 defer cancel() 380 defer xdsR.Close() 381 382 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 383 defer cancel() 384 waitForWatchListener(ctx, t, xdsC, tt.want) 385 }) 386 } 387 } 388 389 // TestXDSResolverWatchCallbackAfterClose tests the case where a service update 390 // from the underlying xdsClient is received after the resolver is closed. 391 func (s) TestXDSResolverWatchCallbackAfterClose(t *testing.T) { 392 xdsR, xdsC, tcc, cancel := testSetup(t, setupOpts{target: target}) 393 defer cancel() 394 395 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 396 defer cancel() 397 waitForWatchListener(ctx, t, xdsC, targetStr) 398 xdsC.InvokeWatchListenerCallback(xdsresource.ListenerUpdate{RouteConfigName: routeStr, HTTPFilters: routerFilterList}, nil) 399 waitForWatchRouteConfig(ctx, t, xdsC, routeStr) 400 401 // Call the watchAPI callback after closing the resolver, and make sure no 402 // update is triggerred on the ClientConn. 403 xdsR.Close() 404 xdsC.InvokeWatchRouteConfigCallback("", xdsresource.RouteConfigUpdate{ 405 VirtualHosts: []*xdsresource.VirtualHost{ 406 { 407 Domains: []string{targetStr}, 408 Routes: []*xdsresource.Route{{Prefix: newStringP(""), WeightedClusters: map[string]xdsresource.WeightedCluster{cluster: {Weight: 1}}}}, 409 }, 410 }, 411 }, nil) 412 413 if gotVal, gotErr := tcc.stateCh.Receive(ctx); gotErr != context.DeadlineExceeded { 414 t.Fatalf("ClientConn.UpdateState called after xdsResolver is closed: %v", gotVal) 415 } 416 } 417 418 // TestXDSResolverCloseClosesXDSClient tests that the XDS resolver's Close 419 // method closes the XDS client. 420 func (s) TestXDSResolverCloseClosesXDSClient(t *testing.T) { 421 xdsR, xdsC, _, cancel := testSetup(t, setupOpts{target: target}) 422 defer cancel() 423 xdsR.Close() 424 if !xdsC.Closed.HasFired() { 425 t.Fatalf("xds client not closed by xds resolver Close method") 426 } 427 } 428 429 // TestXDSResolverBadServiceUpdate tests the case the xdsClient returns a bad 430 // service update. 431 func (s) TestXDSResolverBadServiceUpdate(t *testing.T) { 432 xdsR, xdsC, tcc, cancel := testSetup(t, setupOpts{target: target}) 433 defer xdsR.Close() 434 defer cancel() 435 436 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 437 defer cancel() 438 waitForWatchListener(ctx, t, xdsC, targetStr) 439 xdsC.InvokeWatchListenerCallback(xdsresource.ListenerUpdate{RouteConfigName: routeStr, HTTPFilters: routerFilterList}, nil) 440 waitForWatchRouteConfig(ctx, t, xdsC, routeStr) 441 442 // Invoke the watchAPI callback with a bad service update and wait for the 443 // ReportError method to be called on the ClientConn. 444 suErr := errors.New("bad serviceupdate") 445 xdsC.InvokeWatchRouteConfigCallback("", xdsresource.RouteConfigUpdate{}, suErr) 446 447 if gotErrVal, gotErr := tcc.errorCh.Receive(ctx); gotErr != nil || gotErrVal != suErr { 448 t.Fatalf("ClientConn.ReportError() received %v, want %v", gotErrVal, suErr) 449 } 450 } 451 452 // TestXDSResolverGoodServiceUpdate tests the happy case where the resolver 453 // gets a good service update from the xdsClient. 454 func (s) TestXDSResolverGoodServiceUpdate(t *testing.T) { 455 xdsR, xdsC, tcc, cancel := testSetup(t, setupOpts{target: target}) 456 defer xdsR.Close() 457 defer cancel() 458 459 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 460 defer cancel() 461 waitForWatchListener(ctx, t, xdsC, targetStr) 462 xdsC.InvokeWatchListenerCallback(xdsresource.ListenerUpdate{RouteConfigName: routeStr, HTTPFilters: routerFilterList}, nil) 463 waitForWatchRouteConfig(ctx, t, xdsC, routeStr) 464 defer replaceRandNumGenerator(0)() 465 466 for _, tt := range []struct { 467 routes []*xdsresource.Route 468 wantJSON string 469 wantClusters map[string]bool 470 }{ 471 { 472 routes: []*xdsresource.Route{{Prefix: newStringP(""), WeightedClusters: map[string]xdsresource.WeightedCluster{"test-cluster-1": {Weight: 1}}}}, 473 wantJSON: `{"loadBalancingConfig":[{ 474 "xds_cluster_manager_experimental":{ 475 "children":{ 476 "cluster:test-cluster-1":{ 477 "childPolicy":[{"cds_experimental":{"cluster":"test-cluster-1"}}] 478 } 479 } 480 }}]}`, 481 wantClusters: map[string]bool{"cluster:test-cluster-1": true}, 482 }, 483 { 484 routes: []*xdsresource.Route{{Prefix: newStringP(""), WeightedClusters: map[string]xdsresource.WeightedCluster{ 485 "cluster_1": {Weight: 75}, 486 "cluster_2": {Weight: 25}, 487 }}}, 488 // This update contains the cluster from the previous update as 489 // well as this update, as the previous config selector still 490 // references the old cluster when the new one is pushed. 491 wantJSON: `{"loadBalancingConfig":[{ 492 "xds_cluster_manager_experimental":{ 493 "children":{ 494 "cluster:test-cluster-1":{ 495 "childPolicy":[{"cds_experimental":{"cluster":"test-cluster-1"}}] 496 }, 497 "cluster:cluster_1":{ 498 "childPolicy":[{"cds_experimental":{"cluster":"cluster_1"}}] 499 }, 500 "cluster:cluster_2":{ 501 "childPolicy":[{"cds_experimental":{"cluster":"cluster_2"}}] 502 } 503 } 504 }}]}`, 505 wantClusters: map[string]bool{"cluster:cluster_1": true, "cluster:cluster_2": true}, 506 }, 507 { 508 routes: []*xdsresource.Route{{Prefix: newStringP(""), WeightedClusters: map[string]xdsresource.WeightedCluster{ 509 "cluster_1": {Weight: 75}, 510 "cluster_2": {Weight: 25}, 511 }}}, 512 // With this redundant update, the old config selector has been 513 // stopped, so there are no more references to the first cluster. 514 // Only the second update's clusters should remain. 515 wantJSON: `{"loadBalancingConfig":[{ 516 "xds_cluster_manager_experimental":{ 517 "children":{ 518 "cluster:cluster_1":{ 519 "childPolicy":[{"cds_experimental":{"cluster":"cluster_1"}}] 520 }, 521 "cluster:cluster_2":{ 522 "childPolicy":[{"cds_experimental":{"cluster":"cluster_2"}}] 523 } 524 } 525 }}]}`, 526 wantClusters: map[string]bool{"cluster:cluster_1": true, "cluster:cluster_2": true}, 527 }, 528 } { 529 // Invoke the watchAPI callback with a good service update and wait for the 530 // UpdateState method to be called on the ClientConn. 531 xdsC.InvokeWatchRouteConfigCallback("", xdsresource.RouteConfigUpdate{ 532 VirtualHosts: []*xdsresource.VirtualHost{ 533 { 534 Domains: []string{targetStr}, 535 Routes: tt.routes, 536 }, 537 }, 538 }, nil) 539 540 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 541 defer cancel() 542 gotState, err := tcc.stateCh.Receive(ctx) 543 if err != nil { 544 t.Fatalf("Error waiting for UpdateState to be called: %v", err) 545 } 546 rState := gotState.(resolver.State) 547 if err := rState.ServiceConfig.Err; err != nil { 548 t.Fatalf("ClientConn.UpdateState received error in service config: %v", rState.ServiceConfig.Err) 549 } 550 551 wantSCParsed := internal.ParseServiceConfigForTesting.(func(string) *serviceconfig.ParseResult)(tt.wantJSON) 552 if !internal.EqualServiceConfigForTesting(rState.ServiceConfig.Config, wantSCParsed.Config) { 553 t.Errorf("ClientConn.UpdateState received different service config") 554 t.Error("got: ", cmp.Diff(nil, rState.ServiceConfig.Config)) 555 t.Error("want: ", cmp.Diff(nil, wantSCParsed.Config)) 556 } 557 558 cs := iresolver.GetConfigSelector(rState) 559 if cs == nil { 560 t.Error("received nil config selector") 561 continue 562 } 563 564 pickedClusters := make(map[string]bool) 565 // Odds of picking 75% cluster 100 times in a row: 1 in 3E-13. And 566 // with the random number generator stubbed out, we can rely on this 567 // to be 100% reproducible. 568 for i := 0; i < 100; i++ { 569 res, err := cs.SelectConfig(iresolver.RPCInfo{Context: context.Background()}) 570 if err != nil { 571 t.Fatalf("Unexpected error from cs.SelectConfig(_): %v", err) 572 } 573 cluster := clustermanager.GetPickedClusterForTesting(res.Context) 574 pickedClusters[cluster] = true 575 res.OnCommitted() 576 } 577 if !reflect.DeepEqual(pickedClusters, tt.wantClusters) { 578 t.Errorf("Picked clusters: %v; want: %v", pickedClusters, tt.wantClusters) 579 } 580 } 581 } 582 583 // TestXDSResolverRequestHash tests a case where a resolver receives a RouteConfig update 584 // with a HashPolicy specifying to generate a hash. The configSelector generated should 585 // successfully generate a Hash. 586 func (s) TestXDSResolverRequestHash(t *testing.T) { 587 oldRH := envconfig.XDSRingHash 588 envconfig.XDSRingHash = true 589 defer func() { envconfig.XDSRingHash = oldRH }() 590 591 xdsR, xdsC, tcc, cancel := testSetup(t, setupOpts{target: target}) 592 defer xdsR.Close() 593 defer cancel() 594 595 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 596 defer cancel() 597 waitForWatchListener(ctx, t, xdsC, targetStr) 598 xdsC.InvokeWatchListenerCallback(xdsresource.ListenerUpdate{RouteConfigName: routeStr, HTTPFilters: routerFilterList}, nil) 599 waitForWatchRouteConfig(ctx, t, xdsC, routeStr) 600 // Invoke watchAPI callback with a good service update (with hash policies 601 // specified) and wait for UpdateState method to be called on ClientConn. 602 xdsC.InvokeWatchRouteConfigCallback("", xdsresource.RouteConfigUpdate{ 603 VirtualHosts: []*xdsresource.VirtualHost{ 604 { 605 Domains: []string{targetStr}, 606 Routes: []*xdsresource.Route{{ 607 Prefix: newStringP(""), 608 WeightedClusters: map[string]xdsresource.WeightedCluster{ 609 "cluster_1": {Weight: 75}, 610 "cluster_2": {Weight: 25}, 611 }, 612 HashPolicies: []*xdsresource.HashPolicy{{ 613 HashPolicyType: xdsresource.HashPolicyTypeHeader, 614 HeaderName: ":path", 615 }}, 616 }}, 617 }, 618 }, 619 }, nil) 620 621 ctx, cancel = context.WithTimeout(context.Background(), defaultTestTimeout) 622 defer cancel() 623 gotState, err := tcc.stateCh.Receive(ctx) 624 if err != nil { 625 t.Fatalf("Error waiting for UpdateState to be called: %v", err) 626 } 627 rState := gotState.(resolver.State) 628 cs := iresolver.GetConfigSelector(rState) 629 if cs == nil { 630 t.Error("received nil config selector") 631 } 632 // Selecting a config when there was a hash policy specified in the route 633 // that will be selected should put a request hash in the config's context. 634 res, err := cs.SelectConfig(iresolver.RPCInfo{Context: metadata.NewOutgoingContext(context.Background(), metadata.Pairs(":path", "/products"))}) 635 if err != nil { 636 t.Fatalf("Unexpected error from cs.SelectConfig(_): %v", err) 637 } 638 requestHashGot := ringhash.GetRequestHashForTesting(res.Context) 639 requestHashWant := xxhash.Sum64String("/products") 640 if requestHashGot != requestHashWant { 641 t.Fatalf("requestHashGot = %v, requestHashWant = %v", requestHashGot, requestHashWant) 642 } 643 } 644 645 // TestXDSResolverRemovedWithRPCs tests the case where a config selector sends 646 // an empty update to the resolver after the resource is removed. 647 func (s) TestXDSResolverRemovedWithRPCs(t *testing.T) { 648 xdsR, xdsC, tcc, cancel := testSetup(t, setupOpts{target: target}) 649 defer cancel() 650 defer xdsR.Close() 651 652 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 653 defer cancel() 654 waitForWatchListener(ctx, t, xdsC, targetStr) 655 xdsC.InvokeWatchListenerCallback(xdsresource.ListenerUpdate{RouteConfigName: routeStr, HTTPFilters: routerFilterList}, nil) 656 waitForWatchRouteConfig(ctx, t, xdsC, routeStr) 657 658 // Invoke the watchAPI callback with a good service update and wait for the 659 // UpdateState method to be called on the ClientConn. 660 xdsC.InvokeWatchRouteConfigCallback("", xdsresource.RouteConfigUpdate{ 661 VirtualHosts: []*xdsresource.VirtualHost{ 662 { 663 Domains: []string{targetStr}, 664 Routes: []*xdsresource.Route{{Prefix: newStringP(""), WeightedClusters: map[string]xdsresource.WeightedCluster{"test-cluster-1": {Weight: 1}}}}, 665 }, 666 }, 667 }, nil) 668 669 gotState, err := tcc.stateCh.Receive(ctx) 670 if err != nil { 671 t.Fatalf("Error waiting for UpdateState to be called: %v", err) 672 } 673 rState := gotState.(resolver.State) 674 if err := rState.ServiceConfig.Err; err != nil { 675 t.Fatalf("ClientConn.UpdateState received error in service config: %v", rState.ServiceConfig.Err) 676 } 677 678 // "Make an RPC" by invoking the config selector. 679 cs := iresolver.GetConfigSelector(rState) 680 if cs == nil { 681 t.Fatalf("received nil config selector") 682 } 683 684 res, err := cs.SelectConfig(iresolver.RPCInfo{Context: context.Background()}) 685 if err != nil { 686 t.Fatalf("Unexpected error from cs.SelectConfig(_): %v", err) 687 } 688 689 // Delete the resource 690 suErr := xdsresource.NewErrorf(xdsresource.ErrorTypeResourceNotFound, "resource removed error") 691 xdsC.InvokeWatchRouteConfigCallback("", xdsresource.RouteConfigUpdate{}, suErr) 692 693 if _, err = tcc.stateCh.Receive(ctx); err != nil { 694 t.Fatalf("Error waiting for UpdateState to be called: %v", err) 695 } 696 697 // "Finish the RPC"; this could cause a panic if the resolver doesn't 698 // handle it correctly. 699 res.OnCommitted() 700 } 701 702 // TestXDSResolverRemovedResource tests for proper behavior after a resource is 703 // removed. 704 func (s) TestXDSResolverRemovedResource(t *testing.T) { 705 xdsR, xdsC, tcc, cancel := testSetup(t, setupOpts{target: target}) 706 defer cancel() 707 defer xdsR.Close() 708 709 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 710 defer cancel() 711 waitForWatchListener(ctx, t, xdsC, targetStr) 712 xdsC.InvokeWatchListenerCallback(xdsresource.ListenerUpdate{RouteConfigName: routeStr, HTTPFilters: routerFilterList}, nil) 713 waitForWatchRouteConfig(ctx, t, xdsC, routeStr) 714 715 // Invoke the watchAPI callback with a good service update and wait for the 716 // UpdateState method to be called on the ClientConn. 717 xdsC.InvokeWatchRouteConfigCallback("", xdsresource.RouteConfigUpdate{ 718 VirtualHosts: []*xdsresource.VirtualHost{ 719 { 720 Domains: []string{targetStr}, 721 Routes: []*xdsresource.Route{{Prefix: newStringP(""), WeightedClusters: map[string]xdsresource.WeightedCluster{"test-cluster-1": {Weight: 1}}}}, 722 }, 723 }, 724 }, nil) 725 wantJSON := `{"loadBalancingConfig":[{ 726 "xds_cluster_manager_experimental":{ 727 "children":{ 728 "cluster:test-cluster-1":{ 729 "childPolicy":[{"cds_experimental":{"cluster":"test-cluster-1"}}] 730 } 731 } 732 }}]}` 733 wantSCParsed := internal.ParseServiceConfigForTesting.(func(string) *serviceconfig.ParseResult)(wantJSON) 734 735 gotState, err := tcc.stateCh.Receive(ctx) 736 if err != nil { 737 t.Fatalf("Error waiting for UpdateState to be called: %v", err) 738 } 739 rState := gotState.(resolver.State) 740 if err := rState.ServiceConfig.Err; err != nil { 741 t.Fatalf("ClientConn.UpdateState received error in service config: %v", rState.ServiceConfig.Err) 742 } 743 if !internal.EqualServiceConfigForTesting(rState.ServiceConfig.Config, wantSCParsed.Config) { 744 t.Errorf("ClientConn.UpdateState received different service config") 745 t.Error("got: ", cmp.Diff(nil, rState.ServiceConfig.Config)) 746 t.Error("want: ", cmp.Diff(nil, wantSCParsed.Config)) 747 } 748 749 // "Make an RPC" by invoking the config selector. 750 cs := iresolver.GetConfigSelector(rState) 751 if cs == nil { 752 t.Fatalf("received nil config selector") 753 } 754 755 res, err := cs.SelectConfig(iresolver.RPCInfo{Context: context.Background()}) 756 if err != nil { 757 t.Fatalf("Unexpected error from cs.SelectConfig(_): %v", err) 758 } 759 760 // "Finish the RPC"; this could cause a panic if the resolver doesn't 761 // handle it correctly. 762 res.OnCommitted() 763 764 // Delete the resource. The channel should receive a service config with the 765 // original cluster but with an erroring config selector. 766 suErr := xdsresource.NewErrorf(xdsresource.ErrorTypeResourceNotFound, "resource removed error") 767 xdsC.InvokeWatchRouteConfigCallback("", xdsresource.RouteConfigUpdate{}, suErr) 768 769 if gotState, err = tcc.stateCh.Receive(ctx); err != nil { 770 t.Fatalf("Error waiting for UpdateState to be called: %v", err) 771 } 772 rState = gotState.(resolver.State) 773 if err := rState.ServiceConfig.Err; err != nil { 774 t.Fatalf("ClientConn.UpdateState received error in service config: %v", rState.ServiceConfig.Err) 775 } 776 if !internal.EqualServiceConfigForTesting(rState.ServiceConfig.Config, wantSCParsed.Config) { 777 t.Errorf("ClientConn.UpdateState received different service config") 778 t.Error("got: ", cmp.Diff(nil, rState.ServiceConfig.Config)) 779 t.Error("want: ", cmp.Diff(nil, wantSCParsed.Config)) 780 } 781 782 // "Make another RPC" by invoking the config selector. 783 cs = iresolver.GetConfigSelector(rState) 784 if cs == nil { 785 t.Fatalf("received nil config selector") 786 } 787 788 res, err = cs.SelectConfig(iresolver.RPCInfo{Context: context.Background()}) 789 if err == nil || status.Code(err) != codes.Unavailable { 790 t.Fatalf("Expected UNAVAILABLE error from cs.SelectConfig(_); got %v, %v", res, err) 791 } 792 793 // In the meantime, an empty ServiceConfig update should have been sent. 794 if gotState, err = tcc.stateCh.Receive(ctx); err != nil { 795 t.Fatalf("Error waiting for UpdateState to be called: %v", err) 796 } 797 rState = gotState.(resolver.State) 798 if err := rState.ServiceConfig.Err; err != nil { 799 t.Fatalf("ClientConn.UpdateState received error in service config: %v", rState.ServiceConfig.Err) 800 } 801 wantSCParsed = internal.ParseServiceConfigForTesting.(func(string) *serviceconfig.ParseResult)("{}") 802 if !internal.EqualServiceConfigForTesting(rState.ServiceConfig.Config, wantSCParsed.Config) { 803 t.Errorf("ClientConn.UpdateState received different service config") 804 t.Error("got: ", cmp.Diff(nil, rState.ServiceConfig.Config)) 805 t.Error("want: ", cmp.Diff(nil, wantSCParsed.Config)) 806 } 807 } 808 809 func (s) TestXDSResolverWRR(t *testing.T) { 810 xdsR, xdsC, tcc, cancel := testSetup(t, setupOpts{target: target}) 811 defer xdsR.Close() 812 defer cancel() 813 814 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 815 defer cancel() 816 waitForWatchListener(ctx, t, xdsC, targetStr) 817 xdsC.InvokeWatchListenerCallback(xdsresource.ListenerUpdate{RouteConfigName: routeStr, HTTPFilters: routerFilterList}, nil) 818 waitForWatchRouteConfig(ctx, t, xdsC, routeStr) 819 820 defer func(oldNewWRR func() wrr.WRR) { newWRR = oldNewWRR }(newWRR) 821 newWRR = testutils.NewTestWRR 822 823 // Invoke the watchAPI callback with a good service update and wait for the 824 // UpdateState method to be called on the ClientConn. 825 xdsC.InvokeWatchRouteConfigCallback("", xdsresource.RouteConfigUpdate{ 826 VirtualHosts: []*xdsresource.VirtualHost{ 827 { 828 Domains: []string{targetStr}, 829 Routes: []*xdsresource.Route{{Prefix: newStringP(""), WeightedClusters: map[string]xdsresource.WeightedCluster{ 830 "A": {Weight: 5}, 831 "B": {Weight: 10}, 832 }}}, 833 }, 834 }, 835 }, nil) 836 837 gotState, err := tcc.stateCh.Receive(ctx) 838 if err != nil { 839 t.Fatalf("Error waiting for UpdateState to be called: %v", err) 840 } 841 rState := gotState.(resolver.State) 842 if err := rState.ServiceConfig.Err; err != nil { 843 t.Fatalf("ClientConn.UpdateState received error in service config: %v", rState.ServiceConfig.Err) 844 } 845 846 cs := iresolver.GetConfigSelector(rState) 847 if cs == nil { 848 t.Fatal("received nil config selector") 849 } 850 851 picks := map[string]int{} 852 for i := 0; i < 30; i++ { 853 res, err := cs.SelectConfig(iresolver.RPCInfo{Context: context.Background()}) 854 if err != nil { 855 t.Fatalf("Unexpected error from cs.SelectConfig(_): %v", err) 856 } 857 picks[clustermanager.GetPickedClusterForTesting(res.Context)]++ 858 res.OnCommitted() 859 } 860 want := map[string]int{"cluster:A": 10, "cluster:B": 20} 861 if !reflect.DeepEqual(picks, want) { 862 t.Errorf("picked clusters = %v; want %v", picks, want) 863 } 864 } 865 866 func (s) TestXDSResolverMaxStreamDuration(t *testing.T) { 867 xdsR, xdsC, tcc, cancel := testSetup(t, setupOpts{target: target}) 868 defer xdsR.Close() 869 defer cancel() 870 871 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 872 defer cancel() 873 waitForWatchListener(ctx, t, xdsC, targetStr) 874 xdsC.InvokeWatchListenerCallback(xdsresource.ListenerUpdate{RouteConfigName: routeStr, MaxStreamDuration: time.Second, HTTPFilters: routerFilterList}, nil) 875 waitForWatchRouteConfig(ctx, t, xdsC, routeStr) 876 877 defer func(oldNewWRR func() wrr.WRR) { newWRR = oldNewWRR }(newWRR) 878 newWRR = testutils.NewTestWRR 879 880 // Invoke the watchAPI callback with a good service update and wait for the 881 // UpdateState method to be called on the ClientConn. 882 xdsC.InvokeWatchRouteConfigCallback("", xdsresource.RouteConfigUpdate{ 883 VirtualHosts: []*xdsresource.VirtualHost{ 884 { 885 Domains: []string{targetStr}, 886 Routes: []*xdsresource.Route{{ 887 Prefix: newStringP("/foo"), 888 WeightedClusters: map[string]xdsresource.WeightedCluster{"A": {Weight: 1}}, 889 MaxStreamDuration: newDurationP(5 * time.Second), 890 }, { 891 Prefix: newStringP("/bar"), 892 WeightedClusters: map[string]xdsresource.WeightedCluster{"B": {Weight: 1}}, 893 MaxStreamDuration: newDurationP(0), 894 }, { 895 Prefix: newStringP(""), 896 WeightedClusters: map[string]xdsresource.WeightedCluster{"C": {Weight: 1}}, 897 }}, 898 }, 899 }, 900 }, nil) 901 902 gotState, err := tcc.stateCh.Receive(ctx) 903 if err != nil { 904 t.Fatalf("Error waiting for UpdateState to be called: %v", err) 905 } 906 rState := gotState.(resolver.State) 907 if err := rState.ServiceConfig.Err; err != nil { 908 t.Fatalf("ClientConn.UpdateState received error in service config: %v", rState.ServiceConfig.Err) 909 } 910 911 cs := iresolver.GetConfigSelector(rState) 912 if cs == nil { 913 t.Fatal("received nil config selector") 914 } 915 916 testCases := []struct { 917 name string 918 method string 919 want *time.Duration 920 }{{ 921 name: "RDS setting", 922 method: "/foo/method", 923 want: newDurationP(5 * time.Second), 924 }, { 925 name: "explicit zero in RDS; ignore LDS", 926 method: "/bar/method", 927 want: nil, 928 }, { 929 name: "no config in RDS; fallback to LDS", 930 method: "/baz/method", 931 want: newDurationP(time.Second), 932 }} 933 934 for _, tc := range testCases { 935 t.Run(tc.name, func(t *testing.T) { 936 req := iresolver.RPCInfo{ 937 Method: tc.method, 938 Context: context.Background(), 939 } 940 res, err := cs.SelectConfig(req) 941 if err != nil { 942 t.Errorf("Unexpected error from cs.SelectConfig(%v): %v", req, err) 943 return 944 } 945 res.OnCommitted() 946 got := res.MethodConfig.Timeout 947 if !reflect.DeepEqual(got, tc.want) { 948 t.Errorf("For method %q: res.MethodConfig.Timeout = %v; want %v", tc.method, got, tc.want) 949 } 950 }) 951 } 952 } 953 954 // TestXDSResolverDelayedOnCommitted tests that clusters remain in service 955 // config if RPCs are in flight. 956 func (s) TestXDSResolverDelayedOnCommitted(t *testing.T) { 957 xdsR, xdsC, tcc, cancel := testSetup(t, setupOpts{target: target}) 958 defer xdsR.Close() 959 defer cancel() 960 961 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 962 defer cancel() 963 waitForWatchListener(ctx, t, xdsC, targetStr) 964 xdsC.InvokeWatchListenerCallback(xdsresource.ListenerUpdate{RouteConfigName: routeStr, HTTPFilters: routerFilterList}, nil) 965 waitForWatchRouteConfig(ctx, t, xdsC, routeStr) 966 967 // Invoke the watchAPI callback with a good service update and wait for the 968 // UpdateState method to be called on the ClientConn. 969 xdsC.InvokeWatchRouteConfigCallback("", xdsresource.RouteConfigUpdate{ 970 VirtualHosts: []*xdsresource.VirtualHost{ 971 { 972 Domains: []string{targetStr}, 973 Routes: []*xdsresource.Route{{Prefix: newStringP(""), WeightedClusters: map[string]xdsresource.WeightedCluster{"test-cluster-1": {Weight: 1}}}}, 974 }, 975 }, 976 }, nil) 977 978 gotState, err := tcc.stateCh.Receive(ctx) 979 if err != nil { 980 t.Fatalf("Error waiting for UpdateState to be called: %v", err) 981 } 982 rState := gotState.(resolver.State) 983 if err := rState.ServiceConfig.Err; err != nil { 984 t.Fatalf("ClientConn.UpdateState received error in service config: %v", rState.ServiceConfig.Err) 985 } 986 987 wantJSON := `{"loadBalancingConfig":[{ 988 "xds_cluster_manager_experimental":{ 989 "children":{ 990 "cluster:test-cluster-1":{ 991 "childPolicy":[{"cds_experimental":{"cluster":"test-cluster-1"}}] 992 } 993 } 994 }}]}` 995 wantSCParsed := internal.ParseServiceConfigForTesting.(func(string) *serviceconfig.ParseResult)(wantJSON) 996 if !internal.EqualServiceConfigForTesting(rState.ServiceConfig.Config, wantSCParsed.Config) { 997 t.Errorf("ClientConn.UpdateState received different service config") 998 t.Error("got: ", cmp.Diff(nil, rState.ServiceConfig.Config)) 999 t.Fatal("want: ", cmp.Diff(nil, wantSCParsed.Config)) 1000 } 1001 1002 cs := iresolver.GetConfigSelector(rState) 1003 if cs == nil { 1004 t.Fatal("received nil config selector") 1005 } 1006 1007 res, err := cs.SelectConfig(iresolver.RPCInfo{Context: context.Background()}) 1008 if err != nil { 1009 t.Fatalf("Unexpected error from cs.SelectConfig(_): %v", err) 1010 } 1011 cluster := clustermanager.GetPickedClusterForTesting(res.Context) 1012 if cluster != "cluster:test-cluster-1" { 1013 t.Fatalf("") 1014 } 1015 // delay res.OnCommitted() 1016 1017 // Perform TWO updates to ensure the old config selector does not hold a 1018 // reference to test-cluster-1. 1019 xdsC.InvokeWatchRouteConfigCallback("", xdsresource.RouteConfigUpdate{ 1020 VirtualHosts: []*xdsresource.VirtualHost{ 1021 { 1022 Domains: []string{targetStr}, 1023 Routes: []*xdsresource.Route{{Prefix: newStringP(""), WeightedClusters: map[string]xdsresource.WeightedCluster{"NEW": {Weight: 1}}}}, 1024 }, 1025 }, 1026 }, nil) 1027 tcc.stateCh.Receive(ctx) // Ignore the first update. 1028 1029 xdsC.InvokeWatchRouteConfigCallback("", xdsresource.RouteConfigUpdate{ 1030 VirtualHosts: []*xdsresource.VirtualHost{ 1031 { 1032 Domains: []string{targetStr}, 1033 Routes: []*xdsresource.Route{{Prefix: newStringP(""), WeightedClusters: map[string]xdsresource.WeightedCluster{"NEW": {Weight: 1}}}}, 1034 }, 1035 }, 1036 }, nil) 1037 1038 gotState, err = tcc.stateCh.Receive(ctx) 1039 if err != nil { 1040 t.Fatalf("Error waiting for UpdateState to be called: %v", err) 1041 } 1042 rState = gotState.(resolver.State) 1043 if err := rState.ServiceConfig.Err; err != nil { 1044 t.Fatalf("ClientConn.UpdateState received error in service config: %v", rState.ServiceConfig.Err) 1045 } 1046 wantJSON2 := `{"loadBalancingConfig":[{ 1047 "xds_cluster_manager_experimental":{ 1048 "children":{ 1049 "cluster:test-cluster-1":{ 1050 "childPolicy":[{"cds_experimental":{"cluster":"test-cluster-1"}}] 1051 }, 1052 "cluster:NEW":{ 1053 "childPolicy":[{"cds_experimental":{"cluster":"NEW"}}] 1054 } 1055 } 1056 }}]}` 1057 wantSCParsed2 := internal.ParseServiceConfigForTesting.(func(string) *serviceconfig.ParseResult)(wantJSON2) 1058 if !internal.EqualServiceConfigForTesting(rState.ServiceConfig.Config, wantSCParsed2.Config) { 1059 t.Errorf("ClientConn.UpdateState received different service config") 1060 t.Error("got: ", cmp.Diff(nil, rState.ServiceConfig.Config)) 1061 t.Fatal("want: ", cmp.Diff(nil, wantSCParsed2.Config)) 1062 } 1063 1064 // Invoke OnCommitted; should lead to a service config update that deletes 1065 // test-cluster-1. 1066 res.OnCommitted() 1067 1068 xdsC.InvokeWatchRouteConfigCallback("", xdsresource.RouteConfigUpdate{ 1069 VirtualHosts: []*xdsresource.VirtualHost{ 1070 { 1071 Domains: []string{targetStr}, 1072 Routes: []*xdsresource.Route{{Prefix: newStringP(""), WeightedClusters: map[string]xdsresource.WeightedCluster{"NEW": {Weight: 1}}}}, 1073 }, 1074 }, 1075 }, nil) 1076 gotState, err = tcc.stateCh.Receive(ctx) 1077 if err != nil { 1078 t.Fatalf("Error waiting for UpdateState to be called: %v", err) 1079 } 1080 rState = gotState.(resolver.State) 1081 if err := rState.ServiceConfig.Err; err != nil { 1082 t.Fatalf("ClientConn.UpdateState received error in service config: %v", rState.ServiceConfig.Err) 1083 } 1084 wantJSON3 := `{"loadBalancingConfig":[{ 1085 "xds_cluster_manager_experimental":{ 1086 "children":{ 1087 "cluster:NEW":{ 1088 "childPolicy":[{"cds_experimental":{"cluster":"NEW"}}] 1089 } 1090 } 1091 }}]}` 1092 wantSCParsed3 := internal.ParseServiceConfigForTesting.(func(string) *serviceconfig.ParseResult)(wantJSON3) 1093 if !internal.EqualServiceConfigForTesting(rState.ServiceConfig.Config, wantSCParsed3.Config) { 1094 t.Errorf("ClientConn.UpdateState received different service config") 1095 t.Error("got: ", cmp.Diff(nil, rState.ServiceConfig.Config)) 1096 t.Fatal("want: ", cmp.Diff(nil, wantSCParsed3.Config)) 1097 } 1098 } 1099 1100 // TestXDSResolverUpdates tests the cases where the resolver gets a good update 1101 // after an error, and an error after the good update. 1102 func (s) TestXDSResolverGoodUpdateAfterError(t *testing.T) { 1103 xdsR, xdsC, tcc, cancel := testSetup(t, setupOpts{target: target}) 1104 defer xdsR.Close() 1105 defer cancel() 1106 1107 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 1108 defer cancel() 1109 waitForWatchListener(ctx, t, xdsC, targetStr) 1110 xdsC.InvokeWatchListenerCallback(xdsresource.ListenerUpdate{RouteConfigName: routeStr, HTTPFilters: routerFilterList}, nil) 1111 waitForWatchRouteConfig(ctx, t, xdsC, routeStr) 1112 1113 // Invoke the watchAPI callback with a bad service update and wait for the 1114 // ReportError method to be called on the ClientConn. 1115 suErr := errors.New("bad serviceupdate") 1116 xdsC.InvokeWatchRouteConfigCallback("", xdsresource.RouteConfigUpdate{}, suErr) 1117 1118 if gotErrVal, gotErr := tcc.errorCh.Receive(ctx); gotErr != nil || gotErrVal != suErr { 1119 t.Fatalf("ClientConn.ReportError() received %v, want %v", gotErrVal, suErr) 1120 } 1121 1122 // Invoke the watchAPI callback with a good service update and wait for the 1123 // UpdateState method to be called on the ClientConn. 1124 xdsC.InvokeWatchRouteConfigCallback("", xdsresource.RouteConfigUpdate{ 1125 VirtualHosts: []*xdsresource.VirtualHost{ 1126 { 1127 Domains: []string{targetStr}, 1128 Routes: []*xdsresource.Route{{Prefix: newStringP(""), WeightedClusters: map[string]xdsresource.WeightedCluster{cluster: {Weight: 1}}}}, 1129 }, 1130 }, 1131 }, nil) 1132 gotState, err := tcc.stateCh.Receive(ctx) 1133 if err != nil { 1134 t.Fatalf("Error waiting for UpdateState to be called: %v", err) 1135 } 1136 rState := gotState.(resolver.State) 1137 if err := rState.ServiceConfig.Err; err != nil { 1138 t.Fatalf("ClientConn.UpdateState received error in service config: %v", rState.ServiceConfig.Err) 1139 } 1140 1141 // Invoke the watchAPI callback with a bad service update and wait for the 1142 // ReportError method to be called on the ClientConn. 1143 suErr2 := errors.New("bad serviceupdate 2") 1144 xdsC.InvokeWatchRouteConfigCallback("", xdsresource.RouteConfigUpdate{}, suErr2) 1145 if gotErrVal, gotErr := tcc.errorCh.Receive(ctx); gotErr != nil || gotErrVal != suErr2 { 1146 t.Fatalf("ClientConn.ReportError() received %v, want %v", gotErrVal, suErr2) 1147 } 1148 } 1149 1150 // TestXDSResolverResourceNotFoundError tests the cases where the resolver gets 1151 // a ResourceNotFoundError. It should generate a service config picking 1152 // weighted_target, but no child balancers. 1153 func (s) TestXDSResolverResourceNotFoundError(t *testing.T) { 1154 xdsR, xdsC, tcc, cancel := testSetup(t, setupOpts{target: target}) 1155 defer xdsR.Close() 1156 defer cancel() 1157 1158 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 1159 defer cancel() 1160 waitForWatchListener(ctx, t, xdsC, targetStr) 1161 xdsC.InvokeWatchListenerCallback(xdsresource.ListenerUpdate{RouteConfigName: routeStr, HTTPFilters: routerFilterList}, nil) 1162 waitForWatchRouteConfig(ctx, t, xdsC, routeStr) 1163 1164 // Invoke the watchAPI callback with a bad service update and wait for the 1165 // ReportError method to be called on the ClientConn. 1166 suErr := xdsresource.NewErrorf(xdsresource.ErrorTypeResourceNotFound, "resource removed error") 1167 xdsC.InvokeWatchRouteConfigCallback("", xdsresource.RouteConfigUpdate{}, suErr) 1168 1169 if gotErrVal, gotErr := tcc.errorCh.Receive(ctx); gotErr != context.DeadlineExceeded { 1170 t.Fatalf("ClientConn.ReportError() received %v, %v, want channel recv timeout", gotErrVal, gotErr) 1171 } 1172 1173 ctx, cancel = context.WithTimeout(context.Background(), defaultTestTimeout) 1174 defer cancel() 1175 gotState, err := tcc.stateCh.Receive(ctx) 1176 if err != nil { 1177 t.Fatalf("Error waiting for UpdateState to be called: %v", err) 1178 } 1179 rState := gotState.(resolver.State) 1180 wantParsedConfig := internal.ParseServiceConfigForTesting.(func(string) *serviceconfig.ParseResult)("{}") 1181 if !internal.EqualServiceConfigForTesting(rState.ServiceConfig.Config, wantParsedConfig.Config) { 1182 t.Error("ClientConn.UpdateState got wrong service config") 1183 t.Errorf("gotParsed: %s", cmp.Diff(nil, rState.ServiceConfig.Config)) 1184 t.Errorf("wantParsed: %s", cmp.Diff(nil, wantParsedConfig.Config)) 1185 } 1186 if err := rState.ServiceConfig.Err; err != nil { 1187 t.Fatalf("ClientConn.UpdateState received error in service config: %v", rState.ServiceConfig.Err) 1188 } 1189 } 1190 1191 // TestXDSResolverMultipleLDSUpdates tests the case where two LDS updates with 1192 // the same RDS name to watch are received without an RDS in between. Those LDS 1193 // updates shouldn't trigger service config update. 1194 // 1195 // This test case also makes sure the resolver doesn't panic. 1196 func (s) TestXDSResolverMultipleLDSUpdates(t *testing.T) { 1197 xdsR, xdsC, tcc, cancel := testSetup(t, setupOpts{target: target}) 1198 defer xdsR.Close() 1199 defer cancel() 1200 1201 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 1202 defer cancel() 1203 waitForWatchListener(ctx, t, xdsC, targetStr) 1204 xdsC.InvokeWatchListenerCallback(xdsresource.ListenerUpdate{RouteConfigName: routeStr, HTTPFilters: routerFilterList}, nil) 1205 waitForWatchRouteConfig(ctx, t, xdsC, routeStr) 1206 defer replaceRandNumGenerator(0)() 1207 1208 // Send a new LDS update, with the same fields. 1209 xdsC.InvokeWatchListenerCallback(xdsresource.ListenerUpdate{RouteConfigName: routeStr, HTTPFilters: routerFilterList}, nil) 1210 ctx, cancel = context.WithTimeout(context.Background(), defaultTestShortTimeout) 1211 defer cancel() 1212 // Should NOT trigger a state update. 1213 gotState, err := tcc.stateCh.Receive(ctx) 1214 if err == nil { 1215 t.Fatalf("ClientConn.UpdateState received %v, want timeout error", gotState) 1216 } 1217 1218 // Send a new LDS update, with the same RDS name, but different fields. 1219 xdsC.InvokeWatchListenerCallback(xdsresource.ListenerUpdate{RouteConfigName: routeStr, MaxStreamDuration: time.Second, HTTPFilters: routerFilterList}, nil) 1220 ctx, cancel = context.WithTimeout(context.Background(), defaultTestShortTimeout) 1221 defer cancel() 1222 gotState, err = tcc.stateCh.Receive(ctx) 1223 if err == nil { 1224 t.Fatalf("ClientConn.UpdateState received %v, want timeout error", gotState) 1225 } 1226 } 1227 1228 type filterBuilder struct { 1229 httpfilter.Filter // embedded as we do not need to implement registry / parsing in this test. 1230 path *[]string 1231 } 1232 1233 var _ httpfilter.ClientInterceptorBuilder = &filterBuilder{} 1234 1235 func (fb *filterBuilder) BuildClientInterceptor(config, override httpfilter.FilterConfig) (iresolver.ClientInterceptor, error) { 1236 if config == nil { 1237 panic("unexpected missing config") 1238 } 1239 *fb.path = append(*fb.path, "build:"+config.(filterCfg).s) 1240 err := config.(filterCfg).newStreamErr 1241 if override != nil { 1242 *fb.path = append(*fb.path, "override:"+override.(filterCfg).s) 1243 err = override.(filterCfg).newStreamErr 1244 } 1245 1246 return &filterInterceptor{path: fb.path, s: config.(filterCfg).s, err: err}, nil 1247 } 1248 1249 type filterInterceptor struct { 1250 path *[]string 1251 s string 1252 err error 1253 } 1254 1255 func (fi *filterInterceptor) NewStream(ctx context.Context, ri iresolver.RPCInfo, done func(), newStream func(ctx context.Context, done func()) (iresolver.ClientStream, error)) (iresolver.ClientStream, error) { 1256 *fi.path = append(*fi.path, "newstream:"+fi.s) 1257 if fi.err != nil { 1258 return nil, fi.err 1259 } 1260 d := func() { 1261 *fi.path = append(*fi.path, "done:"+fi.s) 1262 done() 1263 } 1264 cs, err := newStream(ctx, d) 1265 if err != nil { 1266 return nil, err 1267 } 1268 return &clientStream{ClientStream: cs, path: fi.path, s: fi.s}, nil 1269 } 1270 1271 type clientStream struct { 1272 iresolver.ClientStream 1273 path *[]string 1274 s string 1275 } 1276 1277 type filterCfg struct { 1278 httpfilter.FilterConfig 1279 s string 1280 newStreamErr error 1281 } 1282 1283 func (s) TestXDSResolverHTTPFilters(t *testing.T) { 1284 var path []string 1285 testCases := []struct { 1286 name string 1287 ldsFilters []xdsresource.HTTPFilter 1288 vhOverrides map[string]httpfilter.FilterConfig 1289 rtOverrides map[string]httpfilter.FilterConfig 1290 clOverrides map[string]httpfilter.FilterConfig 1291 rpcRes map[string][][]string 1292 selectErr string 1293 newStreamErr string 1294 }{ 1295 { 1296 name: "no router filter", 1297 ldsFilters: []xdsresource.HTTPFilter{ 1298 {Name: "foo", Filter: &filterBuilder{path: &path}, Config: filterCfg{s: "foo1"}}, 1299 }, 1300 rpcRes: map[string][][]string{ 1301 "1": { 1302 {"build:foo1", "override:foo2", "build:bar1", "override:bar2", "newstream:foo1", "newstream:bar1", "done:bar1", "done:foo1"}, 1303 }, 1304 }, 1305 selectErr: "no router filter present", 1306 }, 1307 { 1308 name: "ignored after router filter", 1309 ldsFilters: []xdsresource.HTTPFilter{ 1310 {Name: "foo", Filter: &filterBuilder{path: &path}, Config: filterCfg{s: "foo1"}}, 1311 routerFilter, 1312 {Name: "foo2", Filter: &filterBuilder{path: &path}, Config: filterCfg{s: "foo2"}}, 1313 }, 1314 rpcRes: map[string][][]string{ 1315 "1": { 1316 {"build:foo1", "newstream:foo1", "done:foo1"}, 1317 }, 1318 "2": { 1319 {"build:foo1", "newstream:foo1", "done:foo1"}, 1320 {"build:foo1", "newstream:foo1", "done:foo1"}, 1321 {"build:foo1", "newstream:foo1", "done:foo1"}, 1322 }, 1323 }, 1324 }, 1325 { 1326 name: "NewStream error; ensure earlier interceptor Done is still called", 1327 ldsFilters: []xdsresource.HTTPFilter{ 1328 {Name: "foo", Filter: &filterBuilder{path: &path}, Config: filterCfg{s: "foo1"}}, 1329 {Name: "bar", Filter: &filterBuilder{path: &path}, Config: filterCfg{s: "bar1", newStreamErr: errors.New("bar newstream err")}}, 1330 routerFilter, 1331 }, 1332 rpcRes: map[string][][]string{ 1333 "1": { 1334 {"build:foo1", "build:bar1", "newstream:foo1", "newstream:bar1" /* <err in bar1 NewStream> */, "done:foo1"}, 1335 }, 1336 "2": { 1337 {"build:foo1", "build:bar1", "newstream:foo1", "newstream:bar1" /* <err in bar1 NewSteam> */, "done:foo1"}, 1338 }, 1339 }, 1340 newStreamErr: "bar newstream err", 1341 }, 1342 { 1343 name: "all overrides", 1344 ldsFilters: []xdsresource.HTTPFilter{ 1345 {Name: "foo", Filter: &filterBuilder{path: &path}, Config: filterCfg{s: "foo1", newStreamErr: errors.New("this is overridden to nil")}}, 1346 {Name: "bar", Filter: &filterBuilder{path: &path}, Config: filterCfg{s: "bar1"}}, 1347 routerFilter, 1348 }, 1349 vhOverrides: map[string]httpfilter.FilterConfig{"foo": filterCfg{s: "foo2"}, "bar": filterCfg{s: "bar2"}}, 1350 rtOverrides: map[string]httpfilter.FilterConfig{"foo": filterCfg{s: "foo3"}, "bar": filterCfg{s: "bar3"}}, 1351 clOverrides: map[string]httpfilter.FilterConfig{"foo": filterCfg{s: "foo4"}, "bar": filterCfg{s: "bar4"}}, 1352 rpcRes: map[string][][]string{ 1353 "1": { 1354 {"build:foo1", "override:foo2", "build:bar1", "override:bar2", "newstream:foo1", "newstream:bar1", "done:bar1", "done:foo1"}, 1355 {"build:foo1", "override:foo2", "build:bar1", "override:bar2", "newstream:foo1", "newstream:bar1", "done:bar1", "done:foo1"}, 1356 }, 1357 "2": { 1358 {"build:foo1", "override:foo3", "build:bar1", "override:bar3", "newstream:foo1", "newstream:bar1", "done:bar1", "done:foo1"}, 1359 {"build:foo1", "override:foo4", "build:bar1", "override:bar4", "newstream:foo1", "newstream:bar1", "done:bar1", "done:foo1"}, 1360 {"build:foo1", "override:foo3", "build:bar1", "override:bar3", "newstream:foo1", "newstream:bar1", "done:bar1", "done:foo1"}, 1361 {"build:foo1", "override:foo4", "build:bar1", "override:bar4", "newstream:foo1", "newstream:bar1", "done:bar1", "done:foo1"}, 1362 }, 1363 }, 1364 }, 1365 } 1366 1367 for i, tc := range testCases { 1368 t.Run(tc.name, func(t *testing.T) { 1369 xdsR, xdsC, tcc, cancel := testSetup(t, setupOpts{target: target}) 1370 defer xdsR.Close() 1371 defer cancel() 1372 1373 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 1374 defer cancel() 1375 waitForWatchListener(ctx, t, xdsC, targetStr) 1376 1377 xdsC.InvokeWatchListenerCallback(xdsresource.ListenerUpdate{ 1378 RouteConfigName: routeStr, 1379 HTTPFilters: tc.ldsFilters, 1380 }, nil) 1381 if i == 0 { 1382 waitForWatchRouteConfig(ctx, t, xdsC, routeStr) 1383 } 1384 1385 defer func(oldNewWRR func() wrr.WRR) { newWRR = oldNewWRR }(newWRR) 1386 newWRR = testutils.NewTestWRR 1387 1388 // Invoke the watchAPI callback with a good service update and wait for the 1389 // UpdateState method to be called on the ClientConn. 1390 xdsC.InvokeWatchRouteConfigCallback("", xdsresource.RouteConfigUpdate{ 1391 VirtualHosts: []*xdsresource.VirtualHost{ 1392 { 1393 Domains: []string{targetStr}, 1394 Routes: []*xdsresource.Route{{ 1395 Prefix: newStringP("1"), WeightedClusters: map[string]xdsresource.WeightedCluster{ 1396 "A": {Weight: 1}, 1397 "B": {Weight: 1}, 1398 }, 1399 }, { 1400 Prefix: newStringP("2"), WeightedClusters: map[string]xdsresource.WeightedCluster{ 1401 "A": {Weight: 1}, 1402 "B": {Weight: 1, HTTPFilterConfigOverride: tc.clOverrides}, 1403 }, 1404 HTTPFilterConfigOverride: tc.rtOverrides, 1405 }}, 1406 HTTPFilterConfigOverride: tc.vhOverrides, 1407 }, 1408 }, 1409 }, nil) 1410 1411 gotState, err := tcc.stateCh.Receive(ctx) 1412 if err != nil { 1413 t.Fatalf("Error waiting for UpdateState to be called: %v", err) 1414 } 1415 rState := gotState.(resolver.State) 1416 if err := rState.ServiceConfig.Err; err != nil { 1417 t.Fatalf("ClientConn.UpdateState received error in service config: %v", rState.ServiceConfig.Err) 1418 } 1419 1420 cs := iresolver.GetConfigSelector(rState) 1421 if cs == nil { 1422 t.Fatal("received nil config selector") 1423 } 1424 1425 for method, wants := range tc.rpcRes { 1426 // Order of wants is non-deterministic. 1427 remainingWant := make([][]string, len(wants)) 1428 copy(remainingWant, wants) 1429 for n := range wants { 1430 path = nil 1431 1432 res, err := cs.SelectConfig(iresolver.RPCInfo{Method: method, Context: context.Background()}) 1433 if tc.selectErr != "" { 1434 if err == nil || !strings.Contains(err.Error(), tc.selectErr) { 1435 t.Errorf("SelectConfig(_) = _, %v; want _, Contains(%v)", err, tc.selectErr) 1436 } 1437 if err == nil { 1438 res.OnCommitted() 1439 } 1440 continue 1441 } 1442 if err != nil { 1443 t.Fatalf("Unexpected error from cs.SelectConfig(_): %v", err) 1444 } 1445 var doneFunc func() 1446 _, err = res.Interceptor.NewStream(context.Background(), iresolver.RPCInfo{}, func() {}, func(ctx context.Context, done func()) (iresolver.ClientStream, error) { 1447 doneFunc = done 1448 return nil, nil 1449 }) 1450 if tc.newStreamErr != "" { 1451 if err == nil || !strings.Contains(err.Error(), tc.newStreamErr) { 1452 t.Errorf("NewStream(...) = _, %v; want _, Contains(%v)", err, tc.newStreamErr) 1453 } 1454 if err == nil { 1455 res.OnCommitted() 1456 doneFunc() 1457 } 1458 continue 1459 } 1460 if err != nil { 1461 t.Fatalf("unexpected error from Interceptor.NewStream: %v", err) 1462 1463 } 1464 res.OnCommitted() 1465 doneFunc() 1466 1467 // Confirm the desired path is found in remainingWant, and remove it. 1468 pass := false 1469 for i := range remainingWant { 1470 if reflect.DeepEqual(path, remainingWant[i]) { 1471 remainingWant[i] = remainingWant[len(remainingWant)-1] 1472 remainingWant = remainingWant[:len(remainingWant)-1] 1473 pass = true 1474 break 1475 } 1476 } 1477 if !pass { 1478 t.Errorf("%q:%v - path:\n%v\nwant one of:\n%v", method, n, path, remainingWant) 1479 } 1480 } 1481 } 1482 }) 1483 } 1484 } 1485 1486 func replaceRandNumGenerator(start int64) func() { 1487 nextInt := start 1488 xdsresource.RandInt63n = func(int64) (ret int64) { 1489 ret = nextInt 1490 nextInt++ 1491 return 1492 } 1493 return func() { 1494 xdsresource.RandInt63n = grpcrand.Int63n 1495 } 1496 } 1497 1498 func newDurationP(d time.Duration) *time.Duration { 1499 return &d 1500 }