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