google.golang.org/grpc@v1.62.1/xds/internal/xdsclient/xdsresource/tests/unmarshal_cds_test.go (about)

     1  /*
     2   *
     3   * Copyright 2023 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 tests_test contains test cases for unmarshalling of CDS resources.
    20  package tests_test
    21  
    22  import (
    23  	"encoding/json"
    24  	"testing"
    25  
    26  	"github.com/google/go-cmp/cmp"
    27  	"github.com/google/go-cmp/cmp/cmpopts"
    28  	"google.golang.org/grpc/balancer/leastrequest"
    29  	_ "google.golang.org/grpc/balancer/roundrobin" // To register round_robin load balancer.
    30  	"google.golang.org/grpc/internal/balancer/stub"
    31  	"google.golang.org/grpc/internal/envconfig"
    32  	"google.golang.org/grpc/internal/grpctest"
    33  	iserviceconfig "google.golang.org/grpc/internal/serviceconfig"
    34  	"google.golang.org/grpc/internal/testutils"
    35  	"google.golang.org/grpc/internal/testutils/xds/e2e"
    36  	"google.golang.org/grpc/serviceconfig"
    37  	_ "google.golang.org/grpc/xds" // Register the xDS LB Registry Converters.
    38  	"google.golang.org/grpc/xds/internal/balancer/ringhash"
    39  	"google.golang.org/grpc/xds/internal/balancer/wrrlocality"
    40  	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource"
    41  	"google.golang.org/protobuf/types/known/wrapperspb"
    42  
    43  	v3xdsxdstypepb "github.com/cncf/xds/go/xds/type/v3"
    44  	v3clusterpb "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
    45  	v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
    46  	v3endpointpb "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3"
    47  	v3aggregateclusterpb "github.com/envoyproxy/go-control-plane/envoy/extensions/clusters/aggregate/v3"
    48  	v3ringhashpb "github.com/envoyproxy/go-control-plane/envoy/extensions/load_balancing_policies/ring_hash/v3"
    49  	v3roundrobinpb "github.com/envoyproxy/go-control-plane/envoy/extensions/load_balancing_policies/round_robin/v3"
    50  	v3wrrlocalitypb "github.com/envoyproxy/go-control-plane/envoy/extensions/load_balancing_policies/wrr_locality/v3"
    51  	"google.golang.org/protobuf/proto"
    52  	"google.golang.org/protobuf/types/known/anypb"
    53  	"google.golang.org/protobuf/types/known/structpb"
    54  )
    55  
    56  type s struct {
    57  	grpctest.Tester
    58  }
    59  
    60  func Test(t *testing.T) {
    61  	grpctest.RunSubTests(t, s{})
    62  }
    63  
    64  const (
    65  	clusterName = "clusterName"
    66  	serviceName = "service"
    67  )
    68  
    69  var emptyUpdate = xdsresource.ClusterUpdate{ClusterName: clusterName, LRSServerConfig: xdsresource.ClusterLRSOff}
    70  
    71  func wrrLocality(t *testing.T, m proto.Message) *v3wrrlocalitypb.WrrLocality {
    72  	return &v3wrrlocalitypb.WrrLocality{
    73  		EndpointPickingPolicy: &v3clusterpb.LoadBalancingPolicy{
    74  			Policies: []*v3clusterpb.LoadBalancingPolicy_Policy{
    75  				{
    76  					TypedExtensionConfig: &v3corepb.TypedExtensionConfig{
    77  						TypedConfig: testutils.MarshalAny(t, m),
    78  					},
    79  				},
    80  			},
    81  		},
    82  	}
    83  }
    84  
    85  func wrrLocalityAny(t *testing.T, m proto.Message) *anypb.Any {
    86  	return testutils.MarshalAny(t, wrrLocality(t, m))
    87  }
    88  
    89  type customLBConfig struct {
    90  	serviceconfig.LoadBalancingConfig
    91  }
    92  
    93  // We have this test in a separate test package in order to not take a
    94  // dependency on the internal xDS balancer packages within the xDS Client.
    95  func (s) TestValidateCluster_Success(t *testing.T) {
    96  	const customLBPolicyName = "myorg.MyCustomLeastRequestPolicy"
    97  	stub.Register(customLBPolicyName, stub.BalancerFuncs{
    98  		ParseConfig: func(json.RawMessage) (serviceconfig.LoadBalancingConfig, error) {
    99  			return customLBConfig{}, nil
   100  		},
   101  	})
   102  
   103  	defer func(old bool) { envconfig.LeastRequestLB = old }(envconfig.LeastRequestLB)
   104  	envconfig.LeastRequestLB = true
   105  	tests := []struct {
   106  		name         string
   107  		cluster      *v3clusterpb.Cluster
   108  		wantUpdate   xdsresource.ClusterUpdate
   109  		wantLBConfig *iserviceconfig.BalancerConfig
   110  	}{
   111  		{
   112  			name: "happy-case-logical-dns",
   113  			cluster: &v3clusterpb.Cluster{
   114  				Name:                 clusterName,
   115  				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_LOGICAL_DNS},
   116  				LbPolicy:             v3clusterpb.Cluster_ROUND_ROBIN,
   117  				LoadAssignment: &v3endpointpb.ClusterLoadAssignment{
   118  					Endpoints: []*v3endpointpb.LocalityLbEndpoints{{
   119  						LbEndpoints: []*v3endpointpb.LbEndpoint{{
   120  							HostIdentifier: &v3endpointpb.LbEndpoint_Endpoint{
   121  								Endpoint: &v3endpointpb.Endpoint{
   122  									Address: &v3corepb.Address{
   123  										Address: &v3corepb.Address_SocketAddress{
   124  											SocketAddress: &v3corepb.SocketAddress{
   125  												Address: "dns_host",
   126  												PortSpecifier: &v3corepb.SocketAddress_PortValue{
   127  													PortValue: 8080,
   128  												},
   129  											},
   130  										},
   131  									},
   132  								},
   133  							},
   134  						}},
   135  					}},
   136  				},
   137  			},
   138  			wantUpdate: xdsresource.ClusterUpdate{
   139  				ClusterName: clusterName,
   140  				ClusterType: xdsresource.ClusterTypeLogicalDNS,
   141  				DNSHostName: "dns_host:8080",
   142  			},
   143  			wantLBConfig: &iserviceconfig.BalancerConfig{
   144  				Name: wrrlocality.Name,
   145  				Config: &wrrlocality.LBConfig{
   146  					ChildPolicy: &iserviceconfig.BalancerConfig{
   147  						Name: "round_robin",
   148  					},
   149  				},
   150  			},
   151  		},
   152  		{
   153  			name: "happy-case-aggregate-v3",
   154  			cluster: &v3clusterpb.Cluster{
   155  				Name: clusterName,
   156  				ClusterDiscoveryType: &v3clusterpb.Cluster_ClusterType{
   157  					ClusterType: &v3clusterpb.Cluster_CustomClusterType{
   158  						Name: "envoy.clusters.aggregate",
   159  						TypedConfig: testutils.MarshalAny(t, &v3aggregateclusterpb.ClusterConfig{
   160  							Clusters: []string{"a", "b", "c"},
   161  						}),
   162  					},
   163  				},
   164  				LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
   165  			},
   166  			wantUpdate: xdsresource.ClusterUpdate{
   167  				ClusterName: clusterName, LRSServerConfig: xdsresource.ClusterLRSOff, ClusterType: xdsresource.ClusterTypeAggregate,
   168  				PrioritizedClusterNames: []string{"a", "b", "c"},
   169  			},
   170  			wantLBConfig: &iserviceconfig.BalancerConfig{
   171  				Name: wrrlocality.Name,
   172  				Config: &wrrlocality.LBConfig{
   173  					ChildPolicy: &iserviceconfig.BalancerConfig{
   174  						Name: "round_robin",
   175  					},
   176  				},
   177  			},
   178  		},
   179  		{
   180  			name:       "happy-case-no-service-name-no-lrs",
   181  			cluster:    e2e.DefaultCluster(clusterName, "", e2e.SecurityLevelNone),
   182  			wantUpdate: emptyUpdate,
   183  			wantLBConfig: &iserviceconfig.BalancerConfig{
   184  				Name: wrrlocality.Name,
   185  				Config: &wrrlocality.LBConfig{
   186  					ChildPolicy: &iserviceconfig.BalancerConfig{
   187  						Name: "round_robin",
   188  					},
   189  				},
   190  			},
   191  		},
   192  		{
   193  			name:    "happy-case-no-lrs",
   194  			cluster: e2e.DefaultCluster(clusterName, serviceName, e2e.SecurityLevelNone),
   195  			wantUpdate: xdsresource.ClusterUpdate{
   196  				ClusterName:    clusterName,
   197  				EDSServiceName: serviceName,
   198  			},
   199  			wantLBConfig: &iserviceconfig.BalancerConfig{
   200  				Name: wrrlocality.Name,
   201  				Config: &wrrlocality.LBConfig{
   202  					ChildPolicy: &iserviceconfig.BalancerConfig{
   203  						Name: "round_robin",
   204  					},
   205  				},
   206  			},
   207  		},
   208  		{
   209  			name: "happiest-case",
   210  			cluster: e2e.ClusterResourceWithOptions(e2e.ClusterOptions{
   211  				ClusterName: clusterName,
   212  				ServiceName: serviceName,
   213  				EnableLRS:   true,
   214  			}),
   215  			wantUpdate: xdsresource.ClusterUpdate{
   216  				ClusterName:     clusterName,
   217  				EDSServiceName:  serviceName,
   218  				LRSServerConfig: xdsresource.ClusterLRSServerSelf,
   219  			},
   220  			wantLBConfig: &iserviceconfig.BalancerConfig{
   221  				Name: wrrlocality.Name,
   222  				Config: &wrrlocality.LBConfig{
   223  					ChildPolicy: &iserviceconfig.BalancerConfig{
   224  						Name: "round_robin",
   225  					},
   226  				},
   227  			},
   228  		},
   229  		{
   230  			name: "happiest-case-with-circuitbreakers",
   231  			cluster: func() *v3clusterpb.Cluster {
   232  				c := e2e.ClusterResourceWithOptions(e2e.ClusterOptions{
   233  					ClusterName: clusterName,
   234  					ServiceName: serviceName,
   235  					EnableLRS:   true,
   236  				})
   237  				c.CircuitBreakers = &v3clusterpb.CircuitBreakers{
   238  					Thresholds: []*v3clusterpb.CircuitBreakers_Thresholds{
   239  						{
   240  							Priority:    v3corepb.RoutingPriority_DEFAULT,
   241  							MaxRequests: wrapperspb.UInt32(512),
   242  						},
   243  						{
   244  							Priority:    v3corepb.RoutingPriority_HIGH,
   245  							MaxRequests: nil,
   246  						},
   247  					},
   248  				}
   249  				return c
   250  			}(),
   251  			wantUpdate: xdsresource.ClusterUpdate{
   252  				ClusterName:     clusterName,
   253  				EDSServiceName:  serviceName,
   254  				LRSServerConfig: xdsresource.ClusterLRSServerSelf,
   255  				MaxRequests:     func() *uint32 { i := uint32(512); return &i }(),
   256  			},
   257  			wantLBConfig: &iserviceconfig.BalancerConfig{
   258  				Name: wrrlocality.Name,
   259  				Config: &wrrlocality.LBConfig{
   260  					ChildPolicy: &iserviceconfig.BalancerConfig{
   261  						Name: "round_robin",
   262  					},
   263  				},
   264  			},
   265  		},
   266  		{
   267  			name: "happiest-case-with-ring-hash-lb-policy-with-default-config",
   268  			cluster: func() *v3clusterpb.Cluster {
   269  				c := e2e.DefaultCluster(clusterName, serviceName, e2e.SecurityLevelNone)
   270  				c.LbPolicy = v3clusterpb.Cluster_RING_HASH
   271  				return c
   272  			}(),
   273  			wantUpdate: xdsresource.ClusterUpdate{
   274  				ClusterName:    clusterName,
   275  				EDSServiceName: serviceName,
   276  			},
   277  			wantLBConfig: &iserviceconfig.BalancerConfig{
   278  				Name: "ring_hash_experimental",
   279  				Config: &ringhash.LBConfig{
   280  					MinRingSize: 1024,
   281  					MaxRingSize: 4096,
   282  				},
   283  			},
   284  		},
   285  		{
   286  			name: "happiest-case-with-least-request-lb-policy-with-default-config",
   287  			cluster: &v3clusterpb.Cluster{
   288  				Name:                 clusterName,
   289  				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
   290  				EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
   291  					EdsConfig: &v3corepb.ConfigSource{
   292  						ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
   293  							Ads: &v3corepb.AggregatedConfigSource{},
   294  						},
   295  					},
   296  					ServiceName: serviceName,
   297  				},
   298  				LbPolicy: v3clusterpb.Cluster_LEAST_REQUEST,
   299  			},
   300  			wantUpdate: xdsresource.ClusterUpdate{
   301  				ClusterName: clusterName, EDSServiceName: serviceName,
   302  			},
   303  			wantLBConfig: &iserviceconfig.BalancerConfig{
   304  				Name: "least_request_experimental",
   305  				Config: &leastrequest.LBConfig{
   306  					ChoiceCount: 2,
   307  				},
   308  			},
   309  		},
   310  		{
   311  			name: "happiest-case-with-ring-hash-lb-policy-with-none-default-config",
   312  			cluster: func() *v3clusterpb.Cluster {
   313  				c := e2e.DefaultCluster(clusterName, serviceName, e2e.SecurityLevelNone)
   314  				c.LbPolicy = v3clusterpb.Cluster_RING_HASH
   315  				c.LbConfig = &v3clusterpb.Cluster_RingHashLbConfig_{
   316  					RingHashLbConfig: &v3clusterpb.Cluster_RingHashLbConfig{
   317  						MinimumRingSize: wrapperspb.UInt64(10),
   318  						MaximumRingSize: wrapperspb.UInt64(100),
   319  					},
   320  				}
   321  				return c
   322  			}(),
   323  			wantUpdate: xdsresource.ClusterUpdate{
   324  				ClusterName:    clusterName,
   325  				EDSServiceName: serviceName,
   326  			},
   327  			wantLBConfig: &iserviceconfig.BalancerConfig{
   328  				Name: "ring_hash_experimental",
   329  				Config: &ringhash.LBConfig{
   330  					MinRingSize: 10,
   331  					MaxRingSize: 100,
   332  				},
   333  			},
   334  		},
   335  		{
   336  			name: "happiest-case-with-least-request-lb-policy-with-none-default-config",
   337  			cluster: &v3clusterpb.Cluster{
   338  				Name:                 clusterName,
   339  				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
   340  				EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
   341  					EdsConfig: &v3corepb.ConfigSource{
   342  						ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
   343  							Ads: &v3corepb.AggregatedConfigSource{},
   344  						},
   345  					},
   346  					ServiceName: serviceName,
   347  				},
   348  				LbPolicy: v3clusterpb.Cluster_LEAST_REQUEST,
   349  				LbConfig: &v3clusterpb.Cluster_LeastRequestLbConfig_{
   350  					LeastRequestLbConfig: &v3clusterpb.Cluster_LeastRequestLbConfig{
   351  						ChoiceCount: wrapperspb.UInt32(3),
   352  					},
   353  				},
   354  			},
   355  			wantUpdate: xdsresource.ClusterUpdate{
   356  				ClusterName: clusterName, EDSServiceName: serviceName,
   357  			},
   358  			wantLBConfig: &iserviceconfig.BalancerConfig{
   359  				Name: "least_request_experimental",
   360  				Config: &leastrequest.LBConfig{
   361  					ChoiceCount: 3,
   362  				},
   363  			},
   364  		},
   365  		{
   366  			name: "happiest-case-with-ring-hash-lb-policy-configured-through-LoadBalancingPolicy",
   367  			cluster: &v3clusterpb.Cluster{
   368  				Name:                 clusterName,
   369  				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
   370  				EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
   371  					EdsConfig: &v3corepb.ConfigSource{
   372  						ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
   373  							Ads: &v3corepb.AggregatedConfigSource{},
   374  						},
   375  					},
   376  					ServiceName: serviceName,
   377  				},
   378  				LoadBalancingPolicy: &v3clusterpb.LoadBalancingPolicy{
   379  					Policies: []*v3clusterpb.LoadBalancingPolicy_Policy{
   380  						{
   381  							TypedExtensionConfig: &v3corepb.TypedExtensionConfig{
   382  								TypedConfig: testutils.MarshalAny(t, &v3ringhashpb.RingHash{
   383  									HashFunction:    v3ringhashpb.RingHash_XX_HASH,
   384  									MinimumRingSize: wrapperspb.UInt64(10),
   385  									MaximumRingSize: wrapperspb.UInt64(100),
   386  								}),
   387  							},
   388  						},
   389  					},
   390  				},
   391  			},
   392  			wantUpdate: xdsresource.ClusterUpdate{
   393  				ClusterName:    clusterName,
   394  				EDSServiceName: serviceName,
   395  			},
   396  			wantLBConfig: &iserviceconfig.BalancerConfig{
   397  				Name: "ring_hash_experimental",
   398  				Config: &ringhash.LBConfig{
   399  					MinRingSize: 10,
   400  					MaxRingSize: 100,
   401  				},
   402  			},
   403  		},
   404  		{
   405  			name: "happiest-case-with-wrrlocality-rr-child-configured-through-LoadBalancingPolicy",
   406  			cluster: &v3clusterpb.Cluster{
   407  				Name:                 clusterName,
   408  				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
   409  				EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
   410  					EdsConfig: &v3corepb.ConfigSource{
   411  						ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
   412  							Ads: &v3corepb.AggregatedConfigSource{},
   413  						},
   414  					},
   415  					ServiceName: serviceName,
   416  				},
   417  				LoadBalancingPolicy: &v3clusterpb.LoadBalancingPolicy{
   418  					Policies: []*v3clusterpb.LoadBalancingPolicy_Policy{
   419  						{
   420  							TypedExtensionConfig: &v3corepb.TypedExtensionConfig{
   421  								TypedConfig: wrrLocalityAny(t, &v3roundrobinpb.RoundRobin{}),
   422  							},
   423  						},
   424  					},
   425  				},
   426  			},
   427  			wantUpdate: xdsresource.ClusterUpdate{
   428  				ClusterName:    clusterName,
   429  				EDSServiceName: serviceName,
   430  			},
   431  			wantLBConfig: &iserviceconfig.BalancerConfig{
   432  				Name: wrrlocality.Name,
   433  				Config: &wrrlocality.LBConfig{
   434  					ChildPolicy: &iserviceconfig.BalancerConfig{
   435  						Name: "round_robin",
   436  					},
   437  				},
   438  			},
   439  		},
   440  		{
   441  			name: "happiest-case-with-custom-lb-configured-through-LoadBalancingPolicy",
   442  			cluster: &v3clusterpb.Cluster{
   443  				Name:                 clusterName,
   444  				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
   445  				EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
   446  					EdsConfig: &v3corepb.ConfigSource{
   447  						ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
   448  							Ads: &v3corepb.AggregatedConfigSource{},
   449  						},
   450  					},
   451  					ServiceName: serviceName,
   452  				},
   453  				LoadBalancingPolicy: &v3clusterpb.LoadBalancingPolicy{
   454  					Policies: []*v3clusterpb.LoadBalancingPolicy_Policy{
   455  						{
   456  							TypedExtensionConfig: &v3corepb.TypedExtensionConfig{
   457  								TypedConfig: wrrLocalityAny(t, &v3xdsxdstypepb.TypedStruct{
   458  									TypeUrl: "type.googleapis.com/myorg.MyCustomLeastRequestPolicy",
   459  									Value:   &structpb.Struct{},
   460  								}),
   461  							},
   462  						},
   463  					},
   464  				},
   465  			},
   466  			wantUpdate: xdsresource.ClusterUpdate{
   467  				ClusterName:    clusterName,
   468  				EDSServiceName: serviceName,
   469  			},
   470  			wantLBConfig: &iserviceconfig.BalancerConfig{
   471  				Name: wrrlocality.Name,
   472  				Config: &wrrlocality.LBConfig{
   473  					ChildPolicy: &iserviceconfig.BalancerConfig{
   474  						Name:   "myorg.MyCustomLeastRequestPolicy",
   475  						Config: customLBConfig{},
   476  					},
   477  				},
   478  			},
   479  		},
   480  		{
   481  			name: "load-balancing-policy-takes-precedence-over-lb-policy-and-enum",
   482  			cluster: &v3clusterpb.Cluster{
   483  				Name:                 clusterName,
   484  				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
   485  				EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
   486  					EdsConfig: &v3corepb.ConfigSource{
   487  						ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
   488  							Ads: &v3corepb.AggregatedConfigSource{},
   489  						},
   490  					},
   491  					ServiceName: serviceName,
   492  				},
   493  				LbPolicy: v3clusterpb.Cluster_RING_HASH,
   494  				LbConfig: &v3clusterpb.Cluster_RingHashLbConfig_{
   495  					RingHashLbConfig: &v3clusterpb.Cluster_RingHashLbConfig{
   496  						MinimumRingSize: wrapperspb.UInt64(20),
   497  						MaximumRingSize: wrapperspb.UInt64(200),
   498  					},
   499  				},
   500  				LoadBalancingPolicy: &v3clusterpb.LoadBalancingPolicy{
   501  					Policies: []*v3clusterpb.LoadBalancingPolicy_Policy{
   502  						{
   503  							TypedExtensionConfig: &v3corepb.TypedExtensionConfig{
   504  								TypedConfig: testutils.MarshalAny(t, &v3ringhashpb.RingHash{
   505  									HashFunction:    v3ringhashpb.RingHash_XX_HASH,
   506  									MinimumRingSize: wrapperspb.UInt64(10),
   507  									MaximumRingSize: wrapperspb.UInt64(100),
   508  								}),
   509  							},
   510  						},
   511  					},
   512  				},
   513  			},
   514  			wantUpdate: xdsresource.ClusterUpdate{
   515  				ClusterName:    clusterName,
   516  				EDSServiceName: serviceName,
   517  			},
   518  			wantLBConfig: &iserviceconfig.BalancerConfig{
   519  				Name: "ring_hash_experimental",
   520  				Config: &ringhash.LBConfig{
   521  					MinRingSize: 10,
   522  					MaxRingSize: 100,
   523  				},
   524  			},
   525  		},
   526  	}
   527  
   528  	for _, test := range tests {
   529  		t.Run(test.name, func(t *testing.T) {
   530  			update, err := xdsresource.ValidateClusterAndConstructClusterUpdateForTesting(test.cluster)
   531  			if err != nil {
   532  				t.Errorf("validateClusterAndConstructClusterUpdate(%+v) failed: %v", test.cluster, err)
   533  			}
   534  			// Ignore the raw JSON string into the cluster update. JSON bytes
   535  			// are nondeterministic (whitespace etc.) so we cannot reliably
   536  			// compare JSON bytes in a test. Thus, marshal into a Balancer
   537  			// Config struct and compare on that. Only need to test this JSON
   538  			// emission here, as this covers the possible output space.
   539  			if diff := cmp.Diff(update, test.wantUpdate, cmpopts.EquateEmpty(), cmpopts.IgnoreFields(xdsresource.ClusterUpdate{}, "LBPolicy")); diff != "" {
   540  				t.Errorf("validateClusterAndConstructClusterUpdate(%+v) got diff: %v (-got, +want)", test.cluster, diff)
   541  			}
   542  			bc := &iserviceconfig.BalancerConfig{}
   543  			if err := json.Unmarshal(update.LBPolicy, bc); err != nil {
   544  				t.Fatalf("failed to unmarshal JSON: %v", err)
   545  			}
   546  			if diff := cmp.Diff(bc, test.wantLBConfig); diff != "" {
   547  				t.Fatalf("update.LBConfig got unexpected output, diff (-got +want): %v", diff)
   548  			}
   549  		})
   550  	}
   551  }