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