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 }