google.golang.org/grpc@v1.74.2/xds/internal/balancer/cdsbalancer/cdsbalancer_test.go (about) 1 /* 2 * Copyright 2019 gRPC authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package cdsbalancer 18 19 import ( 20 "context" 21 "encoding/json" 22 "fmt" 23 "net" 24 "strings" 25 "testing" 26 "time" 27 28 "github.com/google/go-cmp/cmp" 29 "github.com/google/uuid" 30 "google.golang.org/grpc" 31 "google.golang.org/grpc/balancer" 32 "google.golang.org/grpc/codes" 33 "google.golang.org/grpc/connectivity" 34 "google.golang.org/grpc/credentials/insecure" 35 "google.golang.org/grpc/internal" 36 "google.golang.org/grpc/internal/balancer/stub" 37 "google.golang.org/grpc/internal/grpctest" 38 "google.golang.org/grpc/internal/stubserver" 39 "google.golang.org/grpc/internal/testutils" 40 "google.golang.org/grpc/internal/testutils/xds/e2e" 41 "google.golang.org/grpc/internal/xds/bootstrap" 42 "google.golang.org/grpc/resolver" 43 "google.golang.org/grpc/resolver/manual" 44 "google.golang.org/grpc/serviceconfig" 45 "google.golang.org/grpc/status" 46 xdsinternal "google.golang.org/grpc/xds/internal" 47 "google.golang.org/grpc/xds/internal/balancer/clusterresolver" 48 "google.golang.org/grpc/xds/internal/xdsclient" 49 "google.golang.org/grpc/xds/internal/xdsclient/xdsresource" 50 "google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version" 51 "google.golang.org/protobuf/types/known/durationpb" 52 "google.golang.org/protobuf/types/known/wrapperspb" 53 54 v3clusterpb "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3" 55 v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" 56 v3endpointpb "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3" 57 v3discoverypb "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3" 58 testgrpc "google.golang.org/grpc/interop/grpc_testing" 59 testpb "google.golang.org/grpc/interop/grpc_testing" 60 61 _ "google.golang.org/grpc/balancer/ringhash" // Register the ring_hash LB policy 62 ) 63 64 const ( 65 clusterName = "cluster1" 66 edsClusterName = clusterName + "-eds" 67 dnsClusterName = clusterName + "-dns" 68 serviceName = "service1" 69 dnsHostName = "dns_host" 70 dnsPort = uint32(8080) 71 defaultTestTimeout = 5 * time.Second 72 defaultTestShortTimeout = 10 * time.Millisecond // For events expected to *not* happen. 73 ) 74 75 type s struct { 76 grpctest.Tester 77 } 78 79 func Test(t *testing.T) { 80 grpctest.RunSubTests(t, s{}) 81 } 82 83 func waitForResourceNames(ctx context.Context, resourceNamesCh chan []string, wantNames []string) error { 84 for ctx.Err() == nil { 85 select { 86 case <-ctx.Done(): 87 case gotNames := <-resourceNamesCh: 88 if cmp.Equal(gotNames, wantNames) { 89 return nil 90 } 91 } 92 } 93 if ctx.Err() != nil { 94 return fmt.Errorf("Timeout when waiting for appropriate Cluster resources to be requested") 95 } 96 return nil 97 } 98 99 // Registers a wrapped cluster_resolver LB policy (child policy of the cds LB 100 // policy) for the duration of this test that retains all the functionality of 101 // the former, but makes certain events available for inspection by the test. 102 // 103 // Returns the following: 104 // - a channel to read received load balancing configuration 105 // - a channel to read received resolver error 106 // - a channel that is closed when ExitIdle() is called 107 // - a channel that is closed when the balancer is closed 108 func registerWrappedClusterResolverPolicy(t *testing.T) (chan serviceconfig.LoadBalancingConfig, chan error, chan struct{}, chan struct{}) { 109 clusterresolverBuilder := balancer.Get(clusterresolver.Name) 110 internal.BalancerUnregister(clusterresolverBuilder.Name()) 111 112 lbCfgCh := make(chan serviceconfig.LoadBalancingConfig, 1) 113 resolverErrCh := make(chan error, 1) 114 exitIdleCh := make(chan struct{}) 115 closeCh := make(chan struct{}) 116 117 stub.Register(clusterresolver.Name, stub.BalancerFuncs{ 118 Init: func(bd *stub.BalancerData) { 119 bd.ChildBalancer = clusterresolverBuilder.Build(bd.ClientConn, bd.BuildOptions) 120 }, 121 ParseConfig: func(lbCfg json.RawMessage) (serviceconfig.LoadBalancingConfig, error) { 122 return clusterresolverBuilder.(balancer.ConfigParser).ParseConfig(lbCfg) 123 }, 124 UpdateClientConnState: func(bd *stub.BalancerData, ccs balancer.ClientConnState) error { 125 select { 126 case lbCfgCh <- ccs.BalancerConfig: 127 default: 128 } 129 return bd.ChildBalancer.UpdateClientConnState(ccs) 130 }, 131 ResolverError: func(bd *stub.BalancerData, err error) { 132 select { 133 case resolverErrCh <- err: 134 default: 135 } 136 bd.ChildBalancer.ResolverError(err) 137 }, 138 ExitIdle: func(bd *stub.BalancerData) { 139 bd.ChildBalancer.ExitIdle() 140 close(exitIdleCh) 141 }, 142 Close: func(bd *stub.BalancerData) { 143 bd.ChildBalancer.Close() 144 close(closeCh) 145 }, 146 }) 147 t.Cleanup(func() { balancer.Register(clusterresolverBuilder) }) 148 149 return lbCfgCh, resolverErrCh, exitIdleCh, closeCh 150 } 151 152 // Registers a wrapped cds LB policy for the duration of this test that retains 153 // all the functionality of the original cds LB policy, but makes the newly 154 // built policy available to the test to directly invoke any balancer methods. 155 // 156 // Returns a channel on which the newly built cds LB policy is written to. 157 func registerWrappedCDSPolicy(t *testing.T) chan balancer.Balancer { 158 cdsBuilder := balancer.Get(cdsName) 159 internal.BalancerUnregister(cdsBuilder.Name()) 160 cdsBalancerCh := make(chan balancer.Balancer, 1) 161 stub.Register(cdsBuilder.Name(), stub.BalancerFuncs{ 162 Init: func(bd *stub.BalancerData) { 163 bal := cdsBuilder.Build(bd.ClientConn, bd.BuildOptions) 164 bd.ChildBalancer = bal 165 cdsBalancerCh <- bal 166 }, 167 ParseConfig: func(lbCfg json.RawMessage) (serviceconfig.LoadBalancingConfig, error) { 168 return cdsBuilder.(balancer.ConfigParser).ParseConfig(lbCfg) 169 }, 170 UpdateClientConnState: func(bd *stub.BalancerData, ccs balancer.ClientConnState) error { 171 return bd.ChildBalancer.UpdateClientConnState(ccs) 172 }, 173 Close: func(bd *stub.BalancerData) { 174 bd.ChildBalancer.Close() 175 }, 176 }) 177 t.Cleanup(func() { balancer.Register(cdsBuilder) }) 178 179 return cdsBalancerCh 180 } 181 182 // Performs the following setup required for tests: 183 // - Spins up an xDS management server 184 // - Creates an xDS client talking to this management server 185 // - Creates a manual resolver that configures the cds LB policy as the 186 // top-level policy, and pushes an initial configuration to it 187 // - Creates a gRPC channel with the above manual resolver 188 // 189 // Returns the following: 190 // - the xDS management server 191 // - the nodeID expected by the management server 192 // - the grpc channel to the test backend service 193 // - the manual resolver configured on the channel 194 // - the xDS client used the grpc channel 195 // - a channel on which requested cluster resource names are sent 196 // - a channel used to signal that previously requested cluster resources are 197 // no longer requested 198 func setupWithManagementServer(t *testing.T) (*e2e.ManagementServer, string, *grpc.ClientConn, *manual.Resolver, xdsclient.XDSClient, chan []string, chan struct{}) { 199 return setupWithManagementServerAndListener(t, nil) 200 } 201 202 // Same as setupWithManagementServer, but also allows the caller to specify 203 // a listener to be used by the management server. 204 func setupWithManagementServerAndListener(t *testing.T, lis net.Listener) (*e2e.ManagementServer, string, *grpc.ClientConn, *manual.Resolver, xdsclient.XDSClient, chan []string, chan struct{}) { 205 t.Helper() 206 207 cdsResourceRequestedCh := make(chan []string, 1) 208 cdsResourceCanceledCh := make(chan struct{}, 1) 209 mgmtServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{ 210 Listener: lis, 211 OnStreamRequest: func(_ int64, req *v3discoverypb.DiscoveryRequest) error { 212 if req.GetTypeUrl() == version.V3ClusterURL { 213 switch len(req.GetResourceNames()) { 214 case 0: 215 select { 216 case cdsResourceCanceledCh <- struct{}{}: 217 default: 218 } 219 default: 220 select { 221 case cdsResourceRequestedCh <- req.GetResourceNames(): 222 default: 223 } 224 } 225 } 226 return nil 227 }, 228 // Required for aggregate clusters as all resources cannot be requested 229 // at once. 230 AllowResourceSubset: true, 231 }) 232 233 // Create bootstrap configuration pointing to the above management server. 234 nodeID := uuid.New().String() 235 bc := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address) 236 237 config, err := bootstrap.NewConfigFromContents(bc) 238 if err != nil { 239 t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err) 240 } 241 pool := xdsclient.NewPool(config) 242 xdsC, xdsClose, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{ 243 Name: t.Name(), 244 }) 245 if err != nil { 246 t.Fatalf("Failed to create xDS client: %v", err) 247 } 248 t.Cleanup(xdsClose) 249 250 r := manual.NewBuilderWithScheme("whatever") 251 jsonSC := fmt.Sprintf(`{ 252 "loadBalancingConfig":[{ 253 "cds_experimental":{ 254 "cluster": "%s" 255 } 256 }] 257 }`, clusterName) 258 scpr := internal.ParseServiceConfig.(func(string) *serviceconfig.ParseResult)(jsonSC) 259 r.InitialState(xdsclient.SetClient(resolver.State{ServiceConfig: scpr}, xdsC)) 260 261 cc, err := grpc.NewClient(r.Scheme()+":///test.service", grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithResolvers(r)) 262 if err != nil { 263 t.Fatalf("grpc.NewClient(%q) = %v", lis.Addr().String(), err) 264 } 265 cc.Connect() 266 t.Cleanup(func() { cc.Close() }) 267 268 return mgmtServer, nodeID, cc, r, xdsC, cdsResourceRequestedCh, cdsResourceCanceledCh 269 } 270 271 // Helper function to compare the load balancing configuration received on the 272 // channel with the expected one. Both configs are marshalled to JSON and then 273 // compared. 274 // 275 // Returns an error if marshalling to JSON fails, or if the load balancing 276 // configurations don't match, or if the context deadline expires before reading 277 // a child policy configuration off of the lbCfgCh. 278 func compareLoadBalancingConfig(ctx context.Context, lbCfgCh chan serviceconfig.LoadBalancingConfig, wantChildCfg serviceconfig.LoadBalancingConfig) error { 279 wantJSON, err := json.Marshal(wantChildCfg) 280 if err != nil { 281 return fmt.Errorf("failed to marshal expected child config to JSON: %v", err) 282 } 283 select { 284 case lbCfg := <-lbCfgCh: 285 gotJSON, err := json.Marshal(lbCfg) 286 if err != nil { 287 return fmt.Errorf("failed to marshal received LB config into JSON: %v", err) 288 } 289 if diff := cmp.Diff(wantJSON, gotJSON); diff != "" { 290 return fmt.Errorf("child policy received unexpected diff in config (-want +got):\n%s", diff) 291 } 292 case <-ctx.Done(): 293 return fmt.Errorf("timeout when waiting for child policy to receive its configuration") 294 } 295 return nil 296 } 297 298 func verifyRPCError(gotErr error, wantCode codes.Code, wantErr, wantNodeID string) error { 299 if gotErr == nil { 300 return fmt.Errorf("RPC succeeded when expecting an error with code %v, message %q and nodeID %q", wantCode, wantErr, wantNodeID) 301 } 302 if gotCode := status.Code(gotErr); gotCode != wantCode { 303 return fmt.Errorf("RPC failed with code: %v, want code %v", gotCode, wantCode) 304 } 305 if !strings.Contains(gotErr.Error(), wantErr) { 306 return fmt.Errorf("RPC failed with error: %v, want %q", gotErr, wantErr) 307 } 308 if !strings.Contains(gotErr.Error(), wantNodeID) { 309 return fmt.Errorf("RPC failed with error: %v, want nodeID %q", gotErr, wantNodeID) 310 } 311 return nil 312 } 313 314 // Tests the functionality that handles LB policy configuration. Verifies that 315 // the appropriate xDS resource is requested corresponding to the provided LB 316 // policy configuration. Also verifies that when the LB policy receives the same 317 // configuration again, it does not send out a new request, and when the 318 // configuration changes, it stops requesting the old cluster resource and 319 // starts requesting the new one. 320 func (s) TestConfigurationUpdate_Success(t *testing.T) { 321 _, _, _, r, xdsClient, cdsResourceRequestedCh, _ := setupWithManagementServer(t) 322 323 // Verify that the specified cluster resource is requested. 324 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 325 defer cancel() 326 wantNames := []string{clusterName} 327 if err := waitForResourceNames(ctx, cdsResourceRequestedCh, wantNames); err != nil { 328 t.Fatal(err) 329 } 330 331 // Push the same configuration again. 332 jsonSC := fmt.Sprintf(`{ 333 "loadBalancingConfig":[{ 334 "cds_experimental":{ 335 "cluster": "%s" 336 } 337 }] 338 }`, clusterName) 339 scpr := internal.ParseServiceConfig.(func(string) *serviceconfig.ParseResult)(jsonSC) 340 r.UpdateState(xdsclient.SetClient(resolver.State{ServiceConfig: scpr}, xdsClient)) 341 342 // Verify that a new CDS request is not sent. 343 sCtx, sCancel := context.WithTimeout(ctx, defaultTestShortTimeout) 344 defer sCancel() 345 select { 346 case <-sCtx.Done(): 347 case gotNames := <-cdsResourceRequestedCh: 348 t.Fatalf("CDS resources %v requested when none expected", gotNames) 349 } 350 351 // Push an updated configuration with a different cluster name. 352 newClusterName := clusterName + "-new" 353 jsonSC = fmt.Sprintf(`{ 354 "loadBalancingConfig":[{ 355 "cds_experimental":{ 356 "cluster": "%s" 357 } 358 }] 359 }`, newClusterName) 360 scpr = internal.ParseServiceConfig.(func(string) *serviceconfig.ParseResult)(jsonSC) 361 r.UpdateState(xdsclient.SetClient(resolver.State{ServiceConfig: scpr}, xdsClient)) 362 363 // Verify that the new cluster name is requested and the old one is no 364 // longer requested. 365 wantNames = []string{newClusterName} 366 if err := waitForResourceNames(ctx, cdsResourceRequestedCh, wantNames); err != nil { 367 t.Fatal(err) 368 } 369 } 370 371 // Tests the case where a configuration with an empty cluster name is pushed to 372 // the CDS LB policy. Verifies that ErrBadResolverState is returned. 373 func (s) TestConfigurationUpdate_EmptyCluster(t *testing.T) { 374 // Setup a management server and an xDS client to talk to it. 375 mgmtServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{}) 376 377 // Create bootstrap configuration pointing to the above management server. 378 nodeID := uuid.New().String() 379 bc := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address) 380 381 config, err := bootstrap.NewConfigFromContents(bc) 382 if err != nil { 383 t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err) 384 } 385 pool := xdsclient.NewPool(config) 386 xdsClient, xdsClose, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{ 387 Name: t.Name(), 388 }) 389 if err != nil { 390 t.Fatalf("Failed to create xDS client: %v", err) 391 } 392 t.Cleanup(xdsClose) 393 394 // Create a manual resolver that configures the CDS LB policy as the 395 // top-level LB policy on the channel, and pushes a configuration with an 396 // empty cluster name. Also, register a callback with the manual resolver to 397 // receive the error returned by the balancer when a configuration with an 398 // empty cluster name is pushed. 399 r := manual.NewBuilderWithScheme("whatever") 400 updateStateErrCh := make(chan error, 1) 401 r.UpdateStateCallback = func(err error) { updateStateErrCh <- err } 402 jsonSC := `{ 403 "loadBalancingConfig":[{ 404 "cds_experimental":{ 405 "cluster": "" 406 } 407 }] 408 }` 409 scpr := internal.ParseServiceConfig.(func(string) *serviceconfig.ParseResult)(jsonSC) 410 r.InitialState(xdsclient.SetClient(resolver.State{ServiceConfig: scpr}, xdsClient)) 411 412 // Create a ClientConn with the above manual resolver. 413 cc, err := grpc.NewClient(r.Scheme()+":///test.service", grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithResolvers(r)) 414 if err != nil { 415 t.Fatalf("grpc.NewClient() failed: %v", err) 416 } 417 cc.Connect() 418 t.Cleanup(func() { cc.Close() }) 419 420 select { 421 case <-time.After(defaultTestTimeout): 422 t.Fatalf("Timed out waiting for error from the LB policy") 423 case err := <-updateStateErrCh: 424 if err != balancer.ErrBadResolverState { 425 t.Fatalf("For a configuration update with an empty cluster name, got error %v from the LB policy, want %v", err, balancer.ErrBadResolverState) 426 } 427 } 428 } 429 430 // Tests the case where a configuration with a missing xDS client is pushed to 431 // the CDS LB policy. Verifies that ErrBadResolverState is returned. 432 func (s) TestConfigurationUpdate_MissingXdsClient(t *testing.T) { 433 // Create a manual resolver that configures the CDS LB policy as the 434 // top-level LB policy on the channel, and pushes a configuration that is 435 // missing the xDS client. Also, register a callback with the manual 436 // resolver to receive the error returned by the balancer. 437 r := manual.NewBuilderWithScheme("whatever") 438 updateStateErrCh := make(chan error, 1) 439 r.UpdateStateCallback = func(err error) { updateStateErrCh <- err } 440 jsonSC := `{ 441 "loadBalancingConfig":[{ 442 "cds_experimental":{ 443 "cluster": "foo" 444 } 445 }] 446 }` 447 scpr := internal.ParseServiceConfig.(func(string) *serviceconfig.ParseResult)(jsonSC) 448 r.InitialState(resolver.State{ServiceConfig: scpr}) 449 450 // Create a ClientConn with the above manual resolver. 451 cc, err := grpc.NewClient(r.Scheme()+":///test.service", grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithResolvers(r)) 452 if err != nil { 453 t.Fatalf("grpc.NewClient() failed: %v", err) 454 } 455 cc.Connect() 456 t.Cleanup(func() { cc.Close() }) 457 458 select { 459 case <-time.After(defaultTestTimeout): 460 t.Fatalf("Timed out waiting for error from the LB policy") 461 case err := <-updateStateErrCh: 462 if err != balancer.ErrBadResolverState { 463 t.Fatalf("For a configuration update missing the xDS client, got error %v from the LB policy, want %v", err, balancer.ErrBadResolverState) 464 } 465 } 466 } 467 468 // Tests success scenarios where the cds LB policy receives a cluster resource 469 // from the management server. Verifies that the load balancing configuration 470 // pushed to the child is as expected. 471 func (s) TestClusterUpdate_Success(t *testing.T) { 472 tests := []struct { 473 name string 474 clusterResource *v3clusterpb.Cluster 475 wantChildCfg serviceconfig.LoadBalancingConfig 476 }{ 477 { 478 name: "happy-case-with-circuit-breakers", 479 clusterResource: func() *v3clusterpb.Cluster { 480 c := e2e.DefaultCluster(clusterName, serviceName, e2e.SecurityLevelNone) 481 c.CircuitBreakers = &v3clusterpb.CircuitBreakers{ 482 Thresholds: []*v3clusterpb.CircuitBreakers_Thresholds{ 483 { 484 Priority: v3corepb.RoutingPriority_DEFAULT, 485 MaxRequests: wrapperspb.UInt32(512), 486 }, 487 { 488 Priority: v3corepb.RoutingPriority_HIGH, 489 MaxRequests: nil, 490 }, 491 }, 492 } 493 return c 494 }(), 495 wantChildCfg: &clusterresolver.LBConfig{ 496 DiscoveryMechanisms: []clusterresolver.DiscoveryMechanism{{ 497 Cluster: clusterName, 498 Type: clusterresolver.DiscoveryMechanismTypeEDS, 499 EDSServiceName: serviceName, 500 MaxConcurrentRequests: newUint32(512), 501 OutlierDetection: json.RawMessage(`{}`), 502 TelemetryLabels: xdsinternal.UnknownCSMLabels, 503 }}, 504 XDSLBPolicy: json.RawMessage(`[{"xds_wrr_locality_experimental": {"childPolicy": [{"round_robin": {}}]}}]`), 505 }, 506 }, 507 { 508 name: "happy-case-with-ring-hash-lb-policy", 509 clusterResource: func() *v3clusterpb.Cluster { 510 c := e2e.ClusterResourceWithOptions(e2e.ClusterOptions{ 511 ClusterName: clusterName, 512 ServiceName: serviceName, 513 SecurityLevel: e2e.SecurityLevelNone, 514 Policy: e2e.LoadBalancingPolicyRingHash, 515 }) 516 c.LbConfig = &v3clusterpb.Cluster_RingHashLbConfig_{ 517 RingHashLbConfig: &v3clusterpb.Cluster_RingHashLbConfig{ 518 MinimumRingSize: &wrapperspb.UInt64Value{Value: 100}, 519 MaximumRingSize: &wrapperspb.UInt64Value{Value: 1000}, 520 }, 521 } 522 return c 523 }(), 524 wantChildCfg: &clusterresolver.LBConfig{ 525 DiscoveryMechanisms: []clusterresolver.DiscoveryMechanism{{ 526 Cluster: clusterName, 527 Type: clusterresolver.DiscoveryMechanismTypeEDS, 528 EDSServiceName: serviceName, 529 OutlierDetection: json.RawMessage(`{}`), 530 TelemetryLabels: xdsinternal.UnknownCSMLabels, 531 }}, 532 XDSLBPolicy: json.RawMessage(`[{"ring_hash_experimental": {"minRingSize":100, "maxRingSize":1000}}]`), 533 }, 534 }, 535 { 536 name: "happy-case-outlier-detection-xds-defaults", // OD proto set but no proto fields set 537 clusterResource: func() *v3clusterpb.Cluster { 538 c := e2e.ClusterResourceWithOptions(e2e.ClusterOptions{ 539 ClusterName: clusterName, 540 ServiceName: serviceName, 541 SecurityLevel: e2e.SecurityLevelNone, 542 Policy: e2e.LoadBalancingPolicyRingHash, 543 }) 544 c.OutlierDetection = &v3clusterpb.OutlierDetection{} 545 return c 546 }(), 547 wantChildCfg: &clusterresolver.LBConfig{ 548 DiscoveryMechanisms: []clusterresolver.DiscoveryMechanism{{ 549 Cluster: clusterName, 550 Type: clusterresolver.DiscoveryMechanismTypeEDS, 551 EDSServiceName: serviceName, 552 OutlierDetection: json.RawMessage(`{"successRateEjection":{}}`), 553 TelemetryLabels: xdsinternal.UnknownCSMLabels, 554 }}, 555 XDSLBPolicy: json.RawMessage(`[{"ring_hash_experimental": {"minRingSize":1024, "maxRingSize":8388608}}]`), 556 }, 557 }, 558 { 559 name: "happy-case-outlier-detection-all-fields-set", 560 clusterResource: func() *v3clusterpb.Cluster { 561 c := e2e.ClusterResourceWithOptions(e2e.ClusterOptions{ 562 ClusterName: clusterName, 563 ServiceName: serviceName, 564 SecurityLevel: e2e.SecurityLevelNone, 565 Policy: e2e.LoadBalancingPolicyRingHash, 566 }) 567 c.OutlierDetection = &v3clusterpb.OutlierDetection{ 568 Interval: durationpb.New(10 * time.Second), 569 BaseEjectionTime: durationpb.New(30 * time.Second), 570 MaxEjectionTime: durationpb.New(300 * time.Second), 571 MaxEjectionPercent: wrapperspb.UInt32(10), 572 SuccessRateStdevFactor: wrapperspb.UInt32(1900), 573 EnforcingSuccessRate: wrapperspb.UInt32(100), 574 SuccessRateMinimumHosts: wrapperspb.UInt32(5), 575 SuccessRateRequestVolume: wrapperspb.UInt32(100), 576 FailurePercentageThreshold: wrapperspb.UInt32(85), 577 EnforcingFailurePercentage: wrapperspb.UInt32(5), 578 FailurePercentageMinimumHosts: wrapperspb.UInt32(5), 579 FailurePercentageRequestVolume: wrapperspb.UInt32(50), 580 } 581 return c 582 }(), 583 wantChildCfg: &clusterresolver.LBConfig{ 584 DiscoveryMechanisms: []clusterresolver.DiscoveryMechanism{{ 585 Cluster: clusterName, 586 Type: clusterresolver.DiscoveryMechanismTypeEDS, 587 EDSServiceName: serviceName, 588 OutlierDetection: json.RawMessage(`{ 589 "interval": "10s", 590 "baseEjectionTime": "30s", 591 "maxEjectionTime": "300s", 592 "maxEjectionPercent": 10, 593 "successRateEjection": { 594 "stdevFactor": 1900, 595 "enforcementPercentage": 100, 596 "minimumHosts": 5, 597 "requestVolume": 100 598 }, 599 "failurePercentageEjection": { 600 "threshold": 85, 601 "enforcementPercentage": 5, 602 "minimumHosts": 5, 603 "requestVolume": 50 604 } 605 }`), 606 TelemetryLabels: xdsinternal.UnknownCSMLabels, 607 }}, 608 XDSLBPolicy: json.RawMessage(`[{"ring_hash_experimental": {"minRingSize":1024, "maxRingSize":8388608}}]`), 609 }, 610 }, 611 } 612 613 for _, test := range tests { 614 t.Run(test.name, func(t *testing.T) { 615 lbCfgCh, _, _, _ := registerWrappedClusterResolverPolicy(t) 616 mgmtServer, nodeID, _, _, _, _, _ := setupWithManagementServer(t) 617 618 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 619 defer cancel() 620 if err := mgmtServer.Update(ctx, e2e.UpdateOptions{ 621 NodeID: nodeID, 622 Clusters: []*v3clusterpb.Cluster{test.clusterResource}, 623 SkipValidation: true, 624 }); err != nil { 625 t.Fatal(err) 626 } 627 628 if err := compareLoadBalancingConfig(ctx, lbCfgCh, test.wantChildCfg); err != nil { 629 t.Fatal(err) 630 } 631 }) 632 } 633 } 634 635 // Tests a single success scenario where the cds LB policy receives a cluster 636 // resource from the management server with LRS enabled. Verifies that the load 637 // balancing configuration pushed to the child is as expected. 638 func (s) TestClusterUpdate_SuccessWithLRS(t *testing.T) { 639 lbCfgCh, _, _, _ := registerWrappedClusterResolverPolicy(t) 640 mgmtServer, nodeID, _, _, _, _, _ := setupWithManagementServer(t) 641 642 clusterResource := e2e.ClusterResourceWithOptions(e2e.ClusterOptions{ 643 ClusterName: clusterName, 644 ServiceName: serviceName, 645 EnableLRS: true, 646 }) 647 lrsServerCfg, err := bootstrap.ServerConfigForTesting(bootstrap.ServerConfigTestingOptions{URI: fmt.Sprintf("passthrough:///%s", mgmtServer.Address)}) 648 if err != nil { 649 t.Fatalf("Failed to create LRS server config for testing: %v", err) 650 } 651 652 wantChildCfg := &clusterresolver.LBConfig{ 653 DiscoveryMechanisms: []clusterresolver.DiscoveryMechanism{{ 654 Cluster: clusterName, 655 Type: clusterresolver.DiscoveryMechanismTypeEDS, 656 EDSServiceName: serviceName, 657 LoadReportingServer: lrsServerCfg, 658 OutlierDetection: json.RawMessage(`{}`), 659 TelemetryLabels: xdsinternal.UnknownCSMLabels, 660 }}, 661 XDSLBPolicy: json.RawMessage(`[{"xds_wrr_locality_experimental": {"childPolicy": [{"round_robin": {}}]}}]`), 662 } 663 664 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 665 defer cancel() 666 if err := mgmtServer.Update(ctx, e2e.UpdateOptions{ 667 NodeID: nodeID, 668 Clusters: []*v3clusterpb.Cluster{clusterResource}, 669 SkipValidation: true, 670 }); err != nil { 671 t.Fatal(err) 672 } 673 674 if err := compareLoadBalancingConfig(ctx, lbCfgCh, wantChildCfg); err != nil { 675 t.Fatal(err) 676 } 677 } 678 679 // Tests scenarios for a bad cluster update received from the management server. 680 // 681 // - when a bad cluster resource update is received without any previous good 682 // update from the management server, the cds LB policy is expected to put 683 // the channel in TRANSIENT_FAILURE. 684 // - when a bad cluster resource update is received after a previous good 685 // update from the management server, the cds LB policy is expected to 686 // continue using the previous good update. 687 func (s) TestClusterUpdate_Failure(t *testing.T) { 688 _, resolverErrCh, _, _ := registerWrappedClusterResolverPolicy(t) 689 mgmtServer, nodeID, cc, _, _, cdsResourceRequestedCh, cdsResourceCanceledCh := setupWithManagementServer(t) 690 691 // Verify that the specified cluster resource is requested. 692 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 693 defer cancel() 694 wantNames := []string{clusterName} 695 if err := waitForResourceNames(ctx, cdsResourceRequestedCh, wantNames); err != nil { 696 t.Fatal(err) 697 } 698 699 // Configure the management server to return a cluster resource that 700 // contains a config_source_specifier for the `lrs_server` field which is not 701 // set to `self`, and hence is expected to be NACKed by the client. 702 cluster := e2e.DefaultCluster(clusterName, serviceName, e2e.SecurityLevelNone) 703 cluster.LrsServer = &v3corepb.ConfigSource{ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{}} 704 resources := e2e.UpdateOptions{ 705 NodeID: nodeID, 706 Clusters: []*v3clusterpb.Cluster{cluster}, 707 SkipValidation: true, 708 } 709 if err := mgmtServer.Update(ctx, resources); err != nil { 710 t.Fatal(err) 711 } 712 713 // Verify that the watch for the cluster resource is not cancelled. 714 sCtx, sCancel := context.WithTimeout(ctx, defaultTestShortTimeout) 715 defer sCancel() 716 select { 717 case <-sCtx.Done(): 718 case <-cdsResourceCanceledCh: 719 t.Fatal("Watch for cluster resource is cancelled when not expected to") 720 } 721 722 testutils.AwaitState(ctx, t, cc, connectivity.TransientFailure) 723 724 // Ensure that the NACK error and the xDS node ID are propagated to the RPC 725 // caller. 726 const wantClusterNACKErr = "unsupported config_source_specifier" 727 client := testgrpc.NewTestServiceClient(cc) 728 _, err := client.EmptyCall(ctx, &testpb.Empty{}) 729 if err := verifyRPCError(err, codes.Unavailable, wantClusterNACKErr, nodeID); err != nil { 730 t.Fatal(err) 731 } 732 733 // Start a test service backend. 734 server := stubserver.StartTestService(t, nil) 735 t.Cleanup(server.Stop) 736 737 // Configure cluster and endpoints resources in the management server. 738 resources = e2e.UpdateOptions{ 739 NodeID: nodeID, 740 Clusters: []*v3clusterpb.Cluster{e2e.DefaultCluster(clusterName, serviceName, e2e.SecurityLevelNone)}, 741 Endpoints: []*v3endpointpb.ClusterLoadAssignment{e2e.DefaultEndpoint(serviceName, "localhost", []uint32{testutils.ParsePort(t, server.Address)})}, 742 SkipValidation: true, 743 } 744 if err := mgmtServer.Update(ctx, resources); err != nil { 745 t.Fatal(err) 746 } 747 748 // Verify that a successful RPC can be made. 749 if _, err := client.EmptyCall(ctx, &testpb.Empty{}, grpc.WaitForReady(true)); err != nil { 750 t.Fatalf("EmptyCall() failed: %v", err) 751 } 752 753 // Send the bad cluster resource again. 754 resources = e2e.UpdateOptions{ 755 NodeID: nodeID, 756 Clusters: []*v3clusterpb.Cluster{cluster}, 757 Endpoints: []*v3endpointpb.ClusterLoadAssignment{e2e.DefaultEndpoint(serviceName, "localhost", []uint32{testutils.ParsePort(t, server.Address)})}, 758 SkipValidation: true, 759 } 760 if err := mgmtServer.Update(ctx, resources); err != nil { 761 t.Fatal(err) 762 } 763 764 // Verify that the watch for the cluster resource is not cancelled. 765 sCtx, sCancel = context.WithTimeout(ctx, defaultTestShortTimeout) 766 defer sCancel() 767 select { 768 case <-sCtx.Done(): 769 case <-cdsResourceCanceledCh: 770 t.Fatal("Watch for cluster resource is cancelled when not expected to") 771 } 772 773 // Verify that a successful RPC can be made, using the previously received 774 // good configuration. 775 if _, err := client.EmptyCall(ctx, &testpb.Empty{}, grpc.WaitForReady(true)); err != nil { 776 t.Fatalf("EmptyCall() failed: %v", err) 777 } 778 779 // Verify that the resolver error is pushed to the child policy. 780 select { 781 case err := <-resolverErrCh: 782 if !strings.Contains(err.Error(), wantClusterNACKErr) { 783 t.Fatalf("Error pushed to child policy is %v, want %v", err, wantClusterNACKErr) 784 } 785 case <-ctx.Done(): 786 t.Fatal("Timeout when waiting for resolver error to be pushed to the child policy") 787 } 788 } 789 790 // Tests the following scenarios for resolver errors: 791 // - when a resolver error is received without any previous good update from the 792 // management server, the cds LB policy is expected to put the channel in 793 // TRANSIENT_FAILURE. 794 // - when a resolver error is received (one that is not a resource-not-found 795 // error), with a previous good update from the management server, the cds LB 796 // policy is expected to push the error down the child policy, but is expected 797 // to continue to use the previously received good configuration. 798 // - when a resolver error is received (one that is a resource-not-found 799 // error, which is usually the case when the LDS resource is removed), 800 // with a previous good update from the management server, the cds LB policy 801 // is expected to push the error down the child policy and put the channel in 802 // TRANSIENT_FAILURE. It is also expected to cancel the CDS watch. 803 func (s) TestResolverError(t *testing.T) { 804 _, resolverErrCh, _, childPolicyCloseCh := registerWrappedClusterResolverPolicy(t) 805 lis := testutils.NewListenerWrapper(t, nil) 806 mgmtServer, nodeID, cc, r, _, cdsResourceRequestedCh, cdsResourceCanceledCh := setupWithManagementServerAndListener(t, lis) 807 808 // Grab the wrapped connection from the listener wrapper. This will be used 809 // to verify the connection is closed. 810 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 811 defer cancel() 812 val, err := lis.NewConnCh.Receive(ctx) 813 if err != nil { 814 t.Fatalf("Failed to receive new connection from wrapped listener: %v", err) 815 } 816 conn := val.(*testutils.ConnWrapper) 817 818 // Verify that the specified cluster resource is requested. 819 wantNames := []string{clusterName} 820 if err := waitForResourceNames(ctx, cdsResourceRequestedCh, wantNames); err != nil { 821 t.Fatal(err) 822 } 823 824 // Push a resolver error that is not a resource-not-found error. Here, we 825 // assume that errors from the xDS client or from the xDS resolver contain 826 // the xDS node ID. 827 resolverErr := fmt.Errorf("[xds node id: %s]: resolver-error-not-a-resource-not-found-error", nodeID) 828 r.CC().ReportError(resolverErr) 829 830 testutils.AwaitState(ctx, t, cc, connectivity.TransientFailure) 831 832 // Drain the resolver error channel. 833 select { 834 case <-resolverErrCh: 835 default: 836 } 837 838 // Ensure that the resolver error is propagated to the RPC caller. 839 client := testgrpc.NewTestServiceClient(cc) 840 _, err = client.EmptyCall(ctx, &testpb.Empty{}) 841 if err := verifyRPCError(err, codes.Unavailable, resolverErr.Error(), nodeID); err != nil { 842 t.Fatal(err) 843 } 844 845 // Also verify that the watch for the cluster resource is not cancelled. 846 sCtx, sCancel := context.WithTimeout(ctx, defaultTestShortTimeout) 847 defer sCancel() 848 select { 849 case <-sCtx.Done(): 850 case <-cdsResourceCanceledCh: 851 t.Fatal("Watch for cluster resource is cancelled when not expected to") 852 } 853 854 // Start a test service backend. 855 server := stubserver.StartTestService(t, nil) 856 t.Cleanup(server.Stop) 857 858 // Configure good cluster and endpoints resources in the management server. 859 resources := e2e.UpdateOptions{ 860 NodeID: nodeID, 861 Clusters: []*v3clusterpb.Cluster{e2e.DefaultCluster(clusterName, serviceName, e2e.SecurityLevelNone)}, 862 Endpoints: []*v3endpointpb.ClusterLoadAssignment{e2e.DefaultEndpoint(serviceName, "localhost", []uint32{testutils.ParsePort(t, server.Address)})}, 863 SkipValidation: true, 864 } 865 if err := mgmtServer.Update(ctx, resources); err != nil { 866 t.Fatal(err) 867 } 868 869 // Verify that a successful RPC can be made. 870 if _, err := client.EmptyCall(ctx, &testpb.Empty{}, grpc.WaitForReady(true)); err != nil { 871 t.Fatalf("EmptyCall() failed: %v", err) 872 } 873 874 // Again push a resolver error that is not a resource-not-found error. 875 r.CC().ReportError(resolverErr) 876 877 // And again verify that the watch for the cluster resource is not 878 // cancelled. 879 sCtx, sCancel = context.WithTimeout(ctx, defaultTestShortTimeout) 880 defer sCancel() 881 select { 882 case <-sCtx.Done(): 883 case <-cdsResourceCanceledCh: 884 t.Fatal("Watch for cluster resource is cancelled when not expected to") 885 } 886 887 // Verify that a successful RPC can be made, using the previously received 888 // good configuration. 889 if _, err := client.EmptyCall(ctx, &testpb.Empty{}, grpc.WaitForReady(true)); err != nil { 890 t.Fatalf("EmptyCall() failed: %v", err) 891 } 892 893 // Verify that the resolver error is pushed to the child policy. 894 select { 895 case err := <-resolverErrCh: 896 if err != resolverErr { 897 t.Fatalf("Error pushed to child policy is %v, want %v", err, resolverErr) 898 } 899 case <-ctx.Done(): 900 t.Fatal("Timeout when waiting for resolver error to be pushed to the child policy") 901 } 902 903 // Push a resource-not-found-error this time around. Our xDS resolver does 904 // not send this error though. When an LDS or RDS resource is missing, the 905 // xDS resolver instead sends an erroring config selector which returns an 906 // error at RPC time with the xDS node ID, for new RPCs. Once ongoing RPCs 907 // complete, the xDS resolver will send an empty service config with no 908 // addresses, which will result in pick_first being configured on the 909 // channel. And pick_first will put the channel in TRANSIENT_FAILURE since 910 // it would have received an update with no addresses. 911 resolverErr = fmt.Errorf("[xds node id: %s]: %w", nodeID, xdsresource.NewError(xdsresource.ErrorTypeResourceNotFound, "xds resource not found error")) 912 r.CC().ReportError(resolverErr) 913 914 // Wait for the CDS resource to be not requested anymore, or the connection 915 // to the management server to be closed (which happens as part of the last 916 // resource watch being canceled). 917 select { 918 case <-ctx.Done(): 919 t.Fatal("Timeout when waiting for CDS resource to be not requested") 920 case <-cdsResourceCanceledCh: 921 case <-conn.CloseCh.C: 922 } 923 924 // Verify that the resolver error is pushed to the child policy. 925 select { 926 case <-childPolicyCloseCh: 927 case <-ctx.Done(): 928 t.Fatal("Timeout when waiting for child policy to be closed") 929 } 930 931 testutils.AwaitState(ctx, t, cc, connectivity.TransientFailure) 932 933 // Ensure that the resolver error is propagated to the RPC caller. 934 _, err = client.EmptyCall(ctx, &testpb.Empty{}) 935 if err := verifyRPCError(err, codes.Unavailable, resolverErr.Error(), nodeID); err != nil { 936 t.Fatal(err) 937 } 938 } 939 940 // Tests scenarios involving removal of a cluster resource from the management 941 // server. 942 // 943 // - when the cluster resource is removed after a previous good 944 // update from the management server, the cds LB policy is expected to put 945 // the channel in TRANSIENT_FAILURE. 946 // - when the cluster resource is re-sent by the management server, RPCs 947 // should start succeeding. 948 func (s) TestClusterUpdate_ResourceNotFound(t *testing.T) { 949 mgmtServer, nodeID, cc, _, _, cdsResourceRequestedCh, cdsResourceCanceledCh := setupWithManagementServer(t) 950 951 // Verify that the specified cluster resource is requested. 952 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 953 defer cancel() 954 wantNames := []string{clusterName} 955 if err := waitForResourceNames(ctx, cdsResourceRequestedCh, wantNames); err != nil { 956 t.Fatal(err) 957 } 958 959 // Start a test service backend. 960 server := stubserver.StartTestService(t, nil) 961 t.Cleanup(server.Stop) 962 963 // Configure cluster and endpoints resources in the management server. 964 resources := e2e.UpdateOptions{ 965 NodeID: nodeID, 966 Clusters: []*v3clusterpb.Cluster{e2e.DefaultCluster(clusterName, serviceName, e2e.SecurityLevelNone)}, 967 Endpoints: []*v3endpointpb.ClusterLoadAssignment{e2e.DefaultEndpoint(serviceName, "localhost", []uint32{testutils.ParsePort(t, server.Address)})}, 968 SkipValidation: true, 969 } 970 if err := mgmtServer.Update(ctx, resources); err != nil { 971 t.Fatal(err) 972 } 973 974 // Verify that a successful RPC can be made. 975 client := testgrpc.NewTestServiceClient(cc) 976 if _, err := client.EmptyCall(ctx, &testpb.Empty{}, grpc.WaitForReady(true)); err != nil { 977 t.Fatalf("EmptyCall() failed: %v", err) 978 } 979 980 // Remove the cluster resource from the management server, triggering a 981 // resource-not-found error. 982 resources.Clusters = nil 983 if err := mgmtServer.Update(ctx, resources); err != nil { 984 t.Fatal(err) 985 } 986 987 // Verify that the watch for the cluster resource is not cancelled. 988 sCtx, sCancel := context.WithTimeout(ctx, defaultTestShortTimeout) 989 defer sCancel() 990 select { 991 case <-sCtx.Done(): 992 case <-cdsResourceCanceledCh: 993 t.Fatal("Watch for cluster resource is cancelled when not expected to") 994 } 995 996 testutils.AwaitState(ctx, t, cc, connectivity.TransientFailure) 997 998 // Ensure RPC fails with Unavailable status code and the error message is 999 // meaningful and contains the xDS node ID. 1000 wantErr := fmt.Sprintf("resource %q of type %q has been removed", clusterName, "ClusterResource") 1001 _, err := client.EmptyCall(ctx, &testpb.Empty{}) 1002 if err := verifyRPCError(err, codes.Unavailable, wantErr, nodeID); err != nil { 1003 t.Fatal(err) 1004 } 1005 1006 // Re-add the cluster resource to the management server. 1007 resources = e2e.UpdateOptions{ 1008 NodeID: nodeID, 1009 Clusters: []*v3clusterpb.Cluster{e2e.DefaultCluster(clusterName, serviceName, e2e.SecurityLevelNone)}, 1010 Endpoints: []*v3endpointpb.ClusterLoadAssignment{e2e.DefaultEndpoint(serviceName, "localhost", []uint32{testutils.ParsePort(t, server.Address)})}, 1011 SkipValidation: true, 1012 } 1013 if err := mgmtServer.Update(ctx, resources); err != nil { 1014 t.Fatal(err) 1015 } 1016 1017 // Verify that a successful RPC can be made. 1018 if _, err := client.EmptyCall(ctx, &testpb.Empty{}, grpc.WaitForReady(true)); err != nil { 1019 t.Fatalf("EmptyCall() failed: %v", err) 1020 } 1021 } 1022 1023 // Tests that closing the cds LB policy results in the the child policy being 1024 // closed. 1025 func (s) TestClose(t *testing.T) { 1026 cdsBalancerCh := registerWrappedCDSPolicy(t) 1027 _, _, _, childPolicyCloseCh := registerWrappedClusterResolverPolicy(t) 1028 mgmtServer, nodeID, cc, _, _, _, _ := setupWithManagementServer(t) 1029 1030 // Start a test service backend. 1031 server := stubserver.StartTestService(t, nil) 1032 t.Cleanup(server.Stop) 1033 1034 // Configure cluster and endpoints resources in the management server. 1035 resources := e2e.UpdateOptions{ 1036 NodeID: nodeID, 1037 Clusters: []*v3clusterpb.Cluster{e2e.DefaultCluster(clusterName, serviceName, e2e.SecurityLevelNone)}, 1038 Endpoints: []*v3endpointpb.ClusterLoadAssignment{e2e.DefaultEndpoint(serviceName, "localhost", []uint32{testutils.ParsePort(t, server.Address)})}, 1039 SkipValidation: true, 1040 } 1041 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 1042 defer cancel() 1043 if err := mgmtServer.Update(ctx, resources); err != nil { 1044 t.Fatal(err) 1045 } 1046 1047 // Verify that a successful RPC can be made. 1048 client := testgrpc.NewTestServiceClient(cc) 1049 if _, err := client.EmptyCall(ctx, &testpb.Empty{}, grpc.WaitForReady(true)); err != nil { 1050 t.Fatalf("EmptyCall() failed: %v", err) 1051 } 1052 1053 // Retrieve the cds LB policy and close it. 1054 var cdsBal balancer.Balancer 1055 select { 1056 case cdsBal = <-cdsBalancerCh: 1057 case <-ctx.Done(): 1058 t.Fatal("Timeout when waiting for cds LB policy to be created") 1059 } 1060 cdsBal.Close() 1061 1062 // Wait for the child policy to be closed. 1063 select { 1064 case <-ctx.Done(): 1065 t.Fatal("Timeout when waiting for the child policy to be closed") 1066 case <-childPolicyCloseCh: 1067 } 1068 } 1069 1070 // Tests that calling ExitIdle on the cds LB policy results in the call being 1071 // propagated to the child policy. 1072 func (s) TestExitIdle(t *testing.T) { 1073 cdsBalancerCh := registerWrappedCDSPolicy(t) 1074 _, _, exitIdleCh, _ := registerWrappedClusterResolverPolicy(t) 1075 mgmtServer, nodeID, cc, _, _, _, _ := setupWithManagementServer(t) 1076 1077 // Start a test service backend. 1078 server := stubserver.StartTestService(t, nil) 1079 t.Cleanup(server.Stop) 1080 1081 // Configure cluster and endpoints resources in the management server. 1082 resources := e2e.UpdateOptions{ 1083 NodeID: nodeID, 1084 Clusters: []*v3clusterpb.Cluster{e2e.DefaultCluster(clusterName, serviceName, e2e.SecurityLevelNone)}, 1085 Endpoints: []*v3endpointpb.ClusterLoadAssignment{e2e.DefaultEndpoint(serviceName, "localhost", []uint32{testutils.ParsePort(t, server.Address)})}, 1086 SkipValidation: true, 1087 } 1088 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 1089 defer cancel() 1090 if err := mgmtServer.Update(ctx, resources); err != nil { 1091 t.Fatal(err) 1092 } 1093 1094 // Verify that a successful RPC can be made. 1095 client := testgrpc.NewTestServiceClient(cc) 1096 if _, err := client.EmptyCall(ctx, &testpb.Empty{}, grpc.WaitForReady(true)); err != nil { 1097 t.Fatalf("EmptyCall() failed: %v", err) 1098 } 1099 1100 // Retrieve the cds LB policy and call ExitIdle() on it. 1101 var cdsBal balancer.Balancer 1102 select { 1103 case cdsBal = <-cdsBalancerCh: 1104 case <-ctx.Done(): 1105 t.Fatal("Timeout when waiting for cds LB policy to be created") 1106 } 1107 cdsBal.ExitIdle() 1108 1109 // Wait for ExitIdle to be called on the child policy. 1110 select { 1111 case <-ctx.Done(): 1112 t.Fatal("Timeout when waiting for the child policy to be closed") 1113 case <-exitIdleCh: 1114 } 1115 } 1116 1117 // TestParseConfig verifies the ParseConfig() method in the CDS balancer. 1118 func (s) TestParseConfig(t *testing.T) { 1119 bb := balancer.Get(cdsName) 1120 if bb == nil { 1121 t.Fatalf("balancer.Get(%q) returned nil", cdsName) 1122 } 1123 parser, ok := bb.(balancer.ConfigParser) 1124 if !ok { 1125 t.Fatalf("balancer %q does not implement the ConfigParser interface", cdsName) 1126 } 1127 1128 tests := []struct { 1129 name string 1130 input json.RawMessage 1131 wantCfg serviceconfig.LoadBalancingConfig 1132 wantErr bool 1133 }{ 1134 { 1135 name: "good-config", 1136 input: json.RawMessage(`{"Cluster": "cluster1"}`), 1137 wantCfg: &lbConfig{ClusterName: "cluster1"}, 1138 }, 1139 { 1140 name: "unknown-fields-in-config", 1141 input: json.RawMessage(`{"Unknown": "foobar"}`), 1142 wantCfg: &lbConfig{ClusterName: ""}, 1143 }, 1144 { 1145 name: "empty-config", 1146 input: json.RawMessage(""), 1147 wantErr: true, 1148 }, 1149 { 1150 name: "bad-config", 1151 input: json.RawMessage(`{"Cluster": 5}`), 1152 wantErr: true, 1153 }, 1154 } 1155 1156 for _, test := range tests { 1157 t.Run(test.name, func(t *testing.T) { 1158 gotCfg, gotErr := parser.ParseConfig(test.input) 1159 if (gotErr != nil) != test.wantErr { 1160 t.Fatalf("ParseConfig(%v) = %v, wantErr %v", string(test.input), gotErr, test.wantErr) 1161 } 1162 if test.wantErr { 1163 return 1164 } 1165 if !cmp.Equal(gotCfg, test.wantCfg) { 1166 t.Fatalf("ParseConfig(%v) = %v, want %v", string(test.input), gotCfg, test.wantCfg) 1167 } 1168 }) 1169 } 1170 } 1171 1172 func newUint32(i uint32) *uint32 { 1173 return &i 1174 }