google.golang.org/grpc@v1.72.2/service_config_test.go (about) 1 /* 2 * 3 * Copyright 2017 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 grpc 20 21 import ( 22 "encoding/json" 23 "fmt" 24 "reflect" 25 "testing" 26 "time" 27 28 "google.golang.org/grpc/balancer" 29 "google.golang.org/grpc/codes" 30 "google.golang.org/grpc/internal/balancer/gracefulswitch" 31 "google.golang.org/grpc/serviceconfig" 32 33 internalserviceconfig "google.golang.org/grpc/internal/serviceconfig" 34 ) 35 36 type parseTestCase struct { 37 name string 38 scjs string 39 wantSC *ServiceConfig 40 wantErr bool 41 } 42 43 func lbConfigFor(t *testing.T, name string, cfg serviceconfig.LoadBalancingConfig) serviceconfig.LoadBalancingConfig { 44 if name == "" { 45 name = "pick_first" 46 cfg = struct { 47 serviceconfig.LoadBalancingConfig 48 }{} 49 } 50 d := []map[string]any{{name: cfg}} 51 strCfg, err := json.Marshal(d) 52 t.Logf("strCfg = %v", string(strCfg)) 53 if err != nil { 54 t.Fatalf("Error parsing config: %v", err) 55 } 56 parsedCfg, err := gracefulswitch.ParseConfig(strCfg) 57 if err != nil { 58 t.Fatalf("Error parsing config: %v", err) 59 } 60 return parsedCfg 61 } 62 63 func runParseTests(t *testing.T, testCases []parseTestCase) { 64 t.Helper() 65 for i, c := range testCases { 66 name := c.name 67 if name == "" { 68 name = fmt.Sprint(i) 69 } 70 t.Run(name, func(t *testing.T) { 71 scpr := parseServiceConfig(c.scjs, defaultMaxCallAttempts) 72 var sc *ServiceConfig 73 sc, _ = scpr.Config.(*ServiceConfig) 74 if !c.wantErr { 75 c.wantSC.rawJSONString = c.scjs 76 } 77 if c.wantErr != (scpr.Err != nil) || !reflect.DeepEqual(sc, c.wantSC) { 78 t.Fatalf("parseServiceConfig(%s) = %+v, %v, want %+v, %v", c.scjs, sc, scpr.Err, c.wantSC, c.wantErr) 79 } 80 }) 81 } 82 } 83 84 type pbbData struct { 85 serviceconfig.LoadBalancingConfig 86 Foo string 87 Bar int 88 } 89 90 type parseBalancerBuilder struct{} 91 92 func (parseBalancerBuilder) Name() string { 93 return "pbb" 94 } 95 96 func (parseBalancerBuilder) ParseConfig(c json.RawMessage) (serviceconfig.LoadBalancingConfig, error) { 97 d := pbbData{} 98 if err := json.Unmarshal(c, &d); err != nil { 99 return nil, err 100 } 101 return d, nil 102 } 103 104 func (parseBalancerBuilder) Build(balancer.ClientConn, balancer.BuildOptions) balancer.Balancer { 105 panic("unimplemented") 106 } 107 108 func init() { 109 balancer.Register(parseBalancerBuilder{}) 110 } 111 112 func (s) TestParseLBConfig(t *testing.T) { 113 testcases := []parseTestCase{ 114 { 115 scjs: `{ 116 "loadBalancingConfig": [{"pbb": { "foo": "hi" } }] 117 }`, 118 wantSC: &ServiceConfig{ 119 Methods: make(map[string]MethodConfig), 120 lbConfig: lbConfigFor(t, "pbb", pbbData{Foo: "hi"}), 121 }, 122 wantErr: false, 123 }, 124 } 125 runParseTests(t, testcases) 126 } 127 128 func (s) TestParseNoLBConfigSupported(t *testing.T) { 129 // We have a loadBalancingConfig field but will not encounter a supported 130 // policy. The config will be considered invalid in this case. 131 testcases := []parseTestCase{ 132 { 133 scjs: `{ 134 "loadBalancingConfig": [{"not_a_balancer1": {} }, {"not_a_balancer2": {}}] 135 }`, 136 wantErr: true, 137 }, { 138 scjs: `{"loadBalancingConfig": []}`, 139 wantErr: true, 140 }, 141 } 142 runParseTests(t, testcases) 143 } 144 145 func (s) TestParseLoadBalancer(t *testing.T) { 146 testcases := []parseTestCase{ 147 { 148 scjs: `{ 149 "loadBalancingPolicy": "round_robin", 150 "methodConfig": [ 151 { 152 "name": [ 153 { 154 "service": "foo", 155 "method": "Bar" 156 } 157 ], 158 "waitForReady": true 159 } 160 ] 161 }`, 162 wantSC: &ServiceConfig{ 163 Methods: map[string]MethodConfig{ 164 "/foo/Bar": { 165 WaitForReady: newBool(true), 166 }, 167 }, 168 lbConfig: lbConfigFor(t, "round_robin", nil), 169 }, 170 wantErr: false, 171 }, 172 { 173 scjs: `{ 174 "loadBalancingPolicy": 1, 175 "methodConfig": [ 176 { 177 "name": [ 178 { 179 "service": "foo", 180 "method": "Bar" 181 } 182 ], 183 "waitForReady": false 184 } 185 ] 186 }`, 187 wantErr: true, 188 }, 189 } 190 runParseTests(t, testcases) 191 } 192 193 func (s) TestParseWaitForReady(t *testing.T) { 194 testcases := []parseTestCase{ 195 { 196 scjs: `{ 197 "methodConfig": [ 198 { 199 "name": [ 200 { 201 "service": "foo", 202 "method": "Bar" 203 } 204 ], 205 "waitForReady": true 206 } 207 ] 208 }`, 209 wantSC: &ServiceConfig{ 210 Methods: map[string]MethodConfig{ 211 "/foo/Bar": { 212 WaitForReady: newBool(true), 213 }, 214 }, 215 lbConfig: lbConfigFor(t, "", nil), 216 }, 217 }, 218 { 219 scjs: `{ 220 "methodConfig": [ 221 { 222 "name": [ 223 { 224 "service": "foo", 225 "method": "Bar" 226 } 227 ], 228 "waitForReady": false 229 } 230 ] 231 }`, 232 wantSC: &ServiceConfig{ 233 Methods: map[string]MethodConfig{ 234 "/foo/Bar": { 235 WaitForReady: newBool(false), 236 }, 237 }, 238 lbConfig: lbConfigFor(t, "", nil), 239 }, 240 }, 241 { 242 scjs: `{ 243 "methodConfig": [ 244 { 245 "name": [ 246 { 247 "service": "foo", 248 "method": "Bar" 249 } 250 ], 251 "waitForReady": fall 252 }, 253 { 254 "name": [ 255 { 256 "service": "foo", 257 "method": "Bar" 258 } 259 ], 260 "waitForReady": true 261 } 262 ] 263 }`, 264 wantErr: true, 265 }, 266 } 267 268 runParseTests(t, testcases) 269 } 270 271 func (s) TestParseTimeOut(t *testing.T) { 272 testcases := []parseTestCase{ 273 { 274 scjs: `{ 275 "methodConfig": [ 276 { 277 "name": [ 278 { 279 "service": "foo", 280 "method": "Bar" 281 } 282 ], 283 "timeout": "1s" 284 } 285 ] 286 }`, 287 wantSC: &ServiceConfig{ 288 Methods: map[string]MethodConfig{ 289 "/foo/Bar": { 290 Timeout: newDuration(time.Second), 291 }, 292 }, 293 lbConfig: lbConfigFor(t, "", nil), 294 }, 295 }, 296 { 297 scjs: `{ 298 "methodConfig": [ 299 { 300 "name": [ 301 { 302 "service": "foo", 303 "method": "Bar" 304 } 305 ], 306 "timeout": "3c" 307 } 308 ] 309 }`, 310 wantErr: true, 311 }, 312 { 313 scjs: `{ 314 "methodConfig": [ 315 { 316 "name": [ 317 { 318 "service": "foo", 319 "method": "Bar" 320 } 321 ], 322 "timeout": "3c" 323 }, 324 { 325 "name": [ 326 { 327 "service": "foo", 328 "method": "Bar" 329 } 330 ], 331 "timeout": "1s" 332 } 333 ] 334 }`, 335 wantErr: true, 336 }, 337 } 338 339 runParseTests(t, testcases) 340 } 341 342 func (s) TestParseMsgSize(t *testing.T) { 343 testcases := []parseTestCase{ 344 { 345 scjs: `{ 346 "methodConfig": [ 347 { 348 "name": [ 349 { 350 "service": "foo", 351 "method": "Bar" 352 } 353 ], 354 "maxRequestMessageBytes": 1024, 355 "maxResponseMessageBytes": 2048 356 } 357 ] 358 }`, 359 wantSC: &ServiceConfig{ 360 Methods: map[string]MethodConfig{ 361 "/foo/Bar": { 362 MaxReqSize: newInt(1024), 363 MaxRespSize: newInt(2048), 364 }, 365 }, 366 lbConfig: lbConfigFor(t, "", nil), 367 }, 368 }, 369 { 370 scjs: `{ 371 "methodConfig": [ 372 { 373 "name": [ 374 { 375 "service": "foo", 376 "method": "Bar" 377 } 378 ], 379 "maxRequestMessageBytes": "1024", 380 "maxResponseMessageBytes": "2048" 381 }, 382 { 383 "name": [ 384 { 385 "service": "foo", 386 "method": "Bar" 387 } 388 ], 389 "maxRequestMessageBytes": 1024, 390 "maxResponseMessageBytes": 2048 391 } 392 ] 393 }`, 394 wantErr: true, 395 }, 396 } 397 398 runParseTests(t, testcases) 399 } 400 func (s) TestParseDefaultMethodConfig(t *testing.T) { 401 dc := &ServiceConfig{ 402 Methods: map[string]MethodConfig{ 403 "": {WaitForReady: newBool(true)}, 404 }, 405 lbConfig: lbConfigFor(t, "", nil), 406 } 407 408 runParseTests(t, []parseTestCase{ 409 { 410 scjs: `{ 411 "methodConfig": [{ 412 "name": [{}], 413 "waitForReady": true 414 }] 415 }`, 416 wantSC: dc, 417 }, 418 { 419 scjs: `{ 420 "methodConfig": [{ 421 "name": [{"service": null}], 422 "waitForReady": true 423 }] 424 }`, 425 wantSC: dc, 426 }, 427 { 428 scjs: `{ 429 "methodConfig": [{ 430 "name": [{"service": ""}], 431 "waitForReady": true 432 }] 433 }`, 434 wantSC: dc, 435 }, 436 { 437 scjs: `{ 438 "methodConfig": [{ 439 "name": [{"method": "Bar"}], 440 "waitForReady": true 441 }] 442 }`, 443 wantErr: true, 444 }, 445 { 446 scjs: `{ 447 "methodConfig": [{ 448 "name": [{"service": "", "method": "Bar"}], 449 "waitForReady": true 450 }] 451 }`, 452 wantErr: true, 453 }, 454 }) 455 } 456 457 func (s) TestParseMethodConfigDuplicatedName(t *testing.T) { 458 runParseTests(t, []parseTestCase{ 459 { 460 scjs: `{ 461 "methodConfig": [{ 462 "name": [ 463 {"service": "foo"}, 464 {"service": "foo"} 465 ], 466 "waitForReady": true 467 }] 468 }`, 469 wantErr: true, 470 }, 471 }) 472 } 473 474 func (s) TestParseRetryPolicy(t *testing.T) { 475 runParseTests(t, []parseTestCase{ 476 { 477 name: "valid", 478 scjs: `{ 479 "methodConfig": [{ 480 "name": [{"service": "foo"}], 481 "retryPolicy": { 482 "maxAttempts": 2, 483 "initialBackoff": "2s", 484 "maxBackoff": "10s", 485 "backoffMultiplier": 2, 486 "retryableStatusCodes": ["UNAVAILABLE"] 487 } 488 }] 489 }`, 490 wantSC: &ServiceConfig{ 491 Methods: map[string]MethodConfig{ 492 "/foo/": { 493 RetryPolicy: &internalserviceconfig.RetryPolicy{ 494 MaxAttempts: 2, 495 InitialBackoff: 2 * time.Second, 496 MaxBackoff: 10 * time.Second, 497 BackoffMultiplier: 2, 498 RetryableStatusCodes: map[codes.Code]bool{codes.Unavailable: true}, 499 }, 500 }, 501 }, 502 lbConfig: lbConfigFor(t, "", nil), 503 }, 504 }, 505 { 506 name: "negative maxAttempts", 507 scjs: `{ 508 "methodConfig": [{ 509 "name": [{"service": "foo"}], 510 "retryPolicy": { 511 "maxAttempts": -1, 512 "initialBackoff": "2s", 513 "maxBackoff": "10s", 514 "backoffMultiplier": 2, 515 "retryableStatusCodes": ["UNAVAILABLE"] 516 } 517 }] 518 }`, 519 wantErr: true, 520 }, 521 { 522 name: "missing maxAttempts", 523 scjs: `{ 524 "methodConfig": [{ 525 "name": [{"service": "foo"}], 526 "retryPolicy": { 527 "initialBackoff": "2s", 528 "maxBackoff": "10s", 529 "backoffMultiplier": 2, 530 "retryableStatusCodes": ["UNAVAILABLE"] 531 } 532 }] 533 }`, 534 wantErr: true, 535 }, 536 { 537 name: "zero initialBackoff", 538 scjs: `{ 539 "methodConfig": [{ 540 "name": [{"service": "foo"}], 541 "retryPolicy": { 542 "maxAttempts": 2, 543 "initialBackoff": "0s", 544 "maxBackoff": "10s", 545 "backoffMultiplier": 2, 546 "retryableStatusCodes": ["UNAVAILABLE"] 547 } 548 }] 549 }`, 550 wantErr: true, 551 }, 552 { 553 name: "zero maxBackoff", 554 scjs: `{ 555 "methodConfig": [{ 556 "name": [{"service": "foo"}], 557 "retryPolicy": { 558 "maxAttempts": 2, 559 "initialBackoff": "2s", 560 "maxBackoff": "0s", 561 "backoffMultiplier": 2, 562 "retryableStatusCodes": ["UNAVAILABLE"] 563 } 564 }] 565 }`, 566 wantErr: true, 567 }, 568 { 569 name: "zero backoffMultiplier", 570 scjs: `{ 571 "methodConfig": [{ 572 "name": [{"service": "foo"}], 573 "retryPolicy": { 574 "maxAttempts": 2, 575 "initialBackoff": "2s", 576 "maxBackoff": "10s", 577 "backoffMultiplier": 0, 578 "retryableStatusCodes": ["UNAVAILABLE"] 579 } 580 }] 581 }`, 582 wantErr: true, 583 }, 584 { 585 name: "no retryable codes", 586 scjs: `{ 587 "methodConfig": [{ 588 "name": [{"service": "foo"}], 589 "retryPolicy": { 590 "maxAttempts": 2, 591 "initialBackoff": "2s", 592 "maxBackoff": "10s", 593 "backoffMultiplier": 2, 594 "retryableStatusCodes": [] 595 } 596 }] 597 }`, 598 wantErr: true, 599 }, 600 }) 601 } 602 603 func newBool(b bool) *bool { 604 return &b 605 } 606 607 func newDuration(b time.Duration) *time.Duration { 608 return &b 609 }