@@ -26,6 +26,7 @@ import (
26
26
"time"
27
27
28
28
"github.com/google/go-cmp/cmp"
29
+ "google.golang.org/grpc/codes"
29
30
"google.golang.org/grpc/credentials/insecure"
30
31
xdscreds "google.golang.org/grpc/credentials/xds"
31
32
"google.golang.org/grpc/internal"
@@ -36,6 +37,7 @@ import (
36
37
"google.golang.org/grpc/internal/wrr"
37
38
"google.golang.org/grpc/resolver"
38
39
"google.golang.org/grpc/serviceconfig"
40
+ "google.golang.org/grpc/status"
39
41
_ "google.golang.org/grpc/xds/internal/balancer/cdsbalancer" // To parse LB config
40
42
"google.golang.org/grpc/xds/internal/balancer/clustermanager"
41
43
"google.golang.org/grpc/xds/internal/client"
@@ -435,6 +437,176 @@ func (s) TestXDSResolverGoodServiceUpdate(t *testing.T) {
435
437
}
436
438
}
437
439
440
+ // TestXDSResolverRemovedWithRPCs tests the case where a config selector sends
441
+ // an empty update to the resolver after the resource is removed.
442
+ func (s ) TestXDSResolverRemovedWithRPCs (t * testing.T ) {
443
+ xdsC := fakeclient .NewClient ()
444
+ xdsR , tcc , cancel := testSetup (t , setupOpts {
445
+ xdsClientFunc : func () (xdsClientInterface , error ) { return xdsC , nil },
446
+ })
447
+ defer cancel ()
448
+ defer xdsR .Close ()
449
+
450
+ ctx , cancel := context .WithTimeout (context .Background (), defaultTestTimeout )
451
+ defer cancel ()
452
+ waitForWatchListener (ctx , t , xdsC , targetStr )
453
+ xdsC .InvokeWatchListenerCallback (xdsclient.ListenerUpdate {RouteConfigName : routeStr }, nil )
454
+ waitForWatchRouteConfig (ctx , t , xdsC , routeStr )
455
+
456
+ // Invoke the watchAPI callback with a good service update and wait for the
457
+ // UpdateState method to be called on the ClientConn.
458
+ xdsC .InvokeWatchRouteConfigCallback (xdsclient.RouteConfigUpdate {
459
+ VirtualHosts : []* xdsclient.VirtualHost {
460
+ {
461
+ Domains : []string {targetStr },
462
+ Routes : []* client.Route {{Prefix : newStringP ("" ), Action : map [string ]uint32 {"test-cluster-1" : 1 }}},
463
+ },
464
+ },
465
+ }, nil )
466
+
467
+ gotState , err := tcc .stateCh .Receive (ctx )
468
+ if err != nil {
469
+ t .Fatalf ("ClientConn.UpdateState returned error: %v" , err )
470
+ }
471
+ rState := gotState .(resolver.State )
472
+ if err := rState .ServiceConfig .Err ; err != nil {
473
+ t .Fatalf ("ClientConn.UpdateState received error in service config: %v" , rState .ServiceConfig .Err )
474
+ }
475
+
476
+ // "Make an RPC" by invoking the config selector.
477
+ cs := iresolver .GetConfigSelector (rState )
478
+ if cs == nil {
479
+ t .Fatalf ("received nil config selector" )
480
+ }
481
+
482
+ res , err := cs .SelectConfig (iresolver.RPCInfo {Context : context .Background ()})
483
+ if err != nil {
484
+ t .Fatalf ("Unexpected error from cs.SelectConfig(_): %v" , err )
485
+ }
486
+
487
+ // Delete the resource
488
+ suErr := xdsclient .NewErrorf (xdsclient .ErrorTypeResourceNotFound , "resource removed error" )
489
+ xdsC .InvokeWatchRouteConfigCallback (xdsclient.RouteConfigUpdate {}, suErr )
490
+
491
+ if _ , err = tcc .stateCh .Receive (ctx ); err != nil {
492
+ t .Fatalf ("ClientConn.UpdateState returned error: %v" , err )
493
+ }
494
+
495
+ // "Finish the RPC"; this could cause a panic if the resolver doesn't
496
+ // handle it correctly.
497
+ res .OnCommitted ()
498
+ }
499
+
500
+ // TestXDSResolverRemovedResource tests for proper behavior after a resource is
501
+ // removed.
502
+ func (s ) TestXDSResolverRemovedResource (t * testing.T ) {
503
+ xdsC := fakeclient .NewClient ()
504
+ xdsR , tcc , cancel := testSetup (t , setupOpts {
505
+ xdsClientFunc : func () (xdsClientInterface , error ) { return xdsC , nil },
506
+ })
507
+ defer cancel ()
508
+ defer xdsR .Close ()
509
+
510
+ ctx , cancel := context .WithTimeout (context .Background (), defaultTestTimeout )
511
+ defer cancel ()
512
+ waitForWatchListener (ctx , t , xdsC , targetStr )
513
+ xdsC .InvokeWatchListenerCallback (xdsclient.ListenerUpdate {RouteConfigName : routeStr }, nil )
514
+ waitForWatchRouteConfig (ctx , t , xdsC , routeStr )
515
+
516
+ // Invoke the watchAPI callback with a good service update and wait for the
517
+ // UpdateState method to be called on the ClientConn.
518
+ xdsC .InvokeWatchRouteConfigCallback (xdsclient.RouteConfigUpdate {
519
+ VirtualHosts : []* xdsclient.VirtualHost {
520
+ {
521
+ Domains : []string {targetStr },
522
+ Routes : []* client.Route {{Prefix : newStringP ("" ), Action : map [string ]uint32 {"test-cluster-1" : 1 }}},
523
+ },
524
+ },
525
+ }, nil )
526
+ wantJSON := `{"loadBalancingConfig":[{
527
+ "xds_cluster_manager_experimental":{
528
+ "children":{
529
+ "test-cluster-1":{
530
+ "childPolicy":[{"cds_experimental":{"cluster":"test-cluster-1"}}]
531
+ }
532
+ }
533
+ }}]}`
534
+ wantSCParsed := internal .ParseServiceConfigForTesting .(func (string ) * serviceconfig.ParseResult )(wantJSON )
535
+
536
+ gotState , err := tcc .stateCh .Receive (ctx )
537
+ if err != nil {
538
+ t .Fatalf ("ClientConn.UpdateState returned error: %v" , err )
539
+ }
540
+ rState := gotState .(resolver.State )
541
+ if err := rState .ServiceConfig .Err ; err != nil {
542
+ t .Fatalf ("ClientConn.UpdateState received error in service config: %v" , rState .ServiceConfig .Err )
543
+ }
544
+ if ! internal .EqualServiceConfigForTesting (rState .ServiceConfig .Config , wantSCParsed .Config ) {
545
+ t .Errorf ("ClientConn.UpdateState received different service config" )
546
+ t .Error ("got: " , cmp .Diff (nil , rState .ServiceConfig .Config ))
547
+ t .Error ("want: " , cmp .Diff (nil , wantSCParsed .Config ))
548
+ }
549
+
550
+ // "Make an RPC" by invoking the config selector.
551
+ cs := iresolver .GetConfigSelector (rState )
552
+ if cs == nil {
553
+ t .Fatalf ("received nil config selector" )
554
+ }
555
+
556
+ res , err := cs .SelectConfig (iresolver.RPCInfo {Context : context .Background ()})
557
+ if err != nil {
558
+ t .Fatalf ("Unexpected error from cs.SelectConfig(_): %v" , err )
559
+ }
560
+
561
+ // "Finish the RPC"; this could cause a panic if the resolver doesn't
562
+ // handle it correctly.
563
+ res .OnCommitted ()
564
+
565
+ // Delete the resource. The channel should receive a service config with the
566
+ // original cluster but with an erroring config selector.
567
+ suErr := xdsclient .NewErrorf (xdsclient .ErrorTypeResourceNotFound , "resource removed error" )
568
+ xdsC .InvokeWatchRouteConfigCallback (xdsclient.RouteConfigUpdate {}, suErr )
569
+
570
+ if gotState , err = tcc .stateCh .Receive (ctx ); err != nil {
571
+ t .Fatalf ("ClientConn.UpdateState returned error: %v" , err )
572
+ }
573
+ rState = gotState .(resolver.State )
574
+ if err := rState .ServiceConfig .Err ; err != nil {
575
+ t .Fatalf ("ClientConn.UpdateState received error in service config: %v" , rState .ServiceConfig .Err )
576
+ }
577
+ if ! internal .EqualServiceConfigForTesting (rState .ServiceConfig .Config , wantSCParsed .Config ) {
578
+ t .Errorf ("ClientConn.UpdateState received different service config" )
579
+ t .Error ("got: " , cmp .Diff (nil , rState .ServiceConfig .Config ))
580
+ t .Error ("want: " , cmp .Diff (nil , wantSCParsed .Config ))
581
+ }
582
+
583
+ // "Make another RPC" by invoking the config selector.
584
+ cs = iresolver .GetConfigSelector (rState )
585
+ if cs == nil {
586
+ t .Fatalf ("received nil config selector" )
587
+ }
588
+
589
+ res , err = cs .SelectConfig (iresolver.RPCInfo {Context : context .Background ()})
590
+ if err == nil || status .Code (err ) != codes .Unavailable {
591
+ t .Fatalf ("Expected UNAVAILABLE error from cs.SelectConfig(_); got %v, %v" , res , err )
592
+ }
593
+
594
+ // In the meantime, an empty ServiceConfig update should have been sent.
595
+ if gotState , err = tcc .stateCh .Receive (ctx ); err != nil {
596
+ t .Fatalf ("ClientConn.UpdateState returned error: %v" , err )
597
+ }
598
+ rState = gotState .(resolver.State )
599
+ if err := rState .ServiceConfig .Err ; err != nil {
600
+ t .Fatalf ("ClientConn.UpdateState received error in service config: %v" , rState .ServiceConfig .Err )
601
+ }
602
+ wantSCParsed = internal .ParseServiceConfigForTesting .(func (string ) * serviceconfig.ParseResult )("{}" )
603
+ if ! internal .EqualServiceConfigForTesting (rState .ServiceConfig .Config , wantSCParsed .Config ) {
604
+ t .Errorf ("ClientConn.UpdateState received different service config" )
605
+ t .Error ("got: " , cmp .Diff (nil , rState .ServiceConfig .Config ))
606
+ t .Error ("want: " , cmp .Diff (nil , wantSCParsed .Config ))
607
+ }
608
+ }
609
+
438
610
func (s ) TestXDSResolverWRR (t * testing.T ) {
439
611
xdsC := fakeclient .NewClient ()
440
612
xdsR , tcc , cancel := testSetup (t , setupOpts {
0 commit comments