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