go.uber.org/yarpc@v1.72.1/yarpcconfig/chooser_test.go (about) 1 // Copyright (c) 2022 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package yarpcconfig_test 22 23 import ( 24 "context" 25 "errors" 26 "strings" 27 "testing" 28 29 "github.com/golang/mock/gomock" 30 "github.com/opentracing/opentracing-go" 31 "github.com/stretchr/testify/assert" 32 "github.com/stretchr/testify/require" 33 "go.uber.org/yarpc" 34 peerapi "go.uber.org/yarpc/api/peer" 35 "go.uber.org/yarpc/api/peer/peertest" 36 "go.uber.org/yarpc/api/transport" 37 "go.uber.org/yarpc/api/transport/transporttest" 38 "go.uber.org/yarpc/internal/interpolate" 39 "go.uber.org/yarpc/internal/testtime" 40 "go.uber.org/yarpc/internal/whitespace" 41 "go.uber.org/yarpc/peer" 42 "go.uber.org/yarpc/peer/hostport" 43 "go.uber.org/yarpc/peer/pendingheap" 44 "go.uber.org/yarpc/peer/roundrobin" 45 "go.uber.org/yarpc/peer/x/peerheap" 46 "go.uber.org/yarpc/transport/http" 47 "go.uber.org/yarpc/transport/tchannel" 48 "go.uber.org/yarpc/yarpcconfig" 49 "go.uber.org/yarpc/yarpctest" 50 ) 51 52 func TestChooserConfigurator(t *testing.T) { 53 tests := []struct { 54 desc string 55 given string 56 env map[string]string 57 overrideConfigurator func() *yarpcconfig.Configurator 58 wantErr []string 59 test func(*testing.T, yarpc.Config) 60 }{ 61 { 62 desc: "single static peer", 63 given: whitespace.Expand(` 64 transports: 65 fake-transport: 66 nop: ":1234" 67 outbounds: 68 their-service: 69 unary: 70 fake-transport: 71 nop: "*.*" 72 peer: 127.0.0.1:8080 73 `), 74 test: func(t *testing.T, c yarpc.Config) { 75 outbound, ok := c.Outbounds["their-service"] 76 require.True(t, ok, "config has outbound") 77 78 require.NotNil(t, outbound.Unary, "must have unary outbound") 79 unary, ok := outbound.Unary.(*yarpctest.FakeOutbound) 80 require.True(t, ok, "unary outbound must be fake outbound") 81 assert.Equal(t, "*.*", unary.NopOption(), "must have configured pattern") 82 83 transports := unary.Transports() 84 require.Equal(t, 1, len(transports), "must have one transport") 85 86 transport, ok := transports[0].(*yarpctest.FakeTransport) 87 require.True(t, ok, "must be a fake transport") 88 assert.Equal(t, ":1234", transport.NopOption(), "transport configured") 89 90 require.NotNil(t, unary.Chooser(), "must have chooser") 91 chooser, ok := unary.Chooser().(*peer.Single) 92 require.True(t, ok, "unary chooser must be a single peer chooser") 93 94 dispatcher := yarpc.NewDispatcher(c) 95 assert.NoError(t, dispatcher.Start(), "error starting") 96 assert.NoError(t, dispatcher.Stop(), "error stopping") 97 98 _ = chooser 99 }, 100 }, 101 { 102 desc: "custom chooser", 103 given: whitespace.Expand(` 104 transports: 105 fake-transport: 106 nop: ":1234" 107 outbounds: 108 their-service: 109 unary: 110 fake-transport: 111 fake-chooser: 112 nop: "*.*" 113 `), 114 test: func(t *testing.T, c yarpc.Config) { 115 outbound, ok := c.Outbounds["their-service"] 116 require.True(t, ok, "config has outbound") 117 118 require.NotNil(t, outbound.Unary, "must have unary outbound") 119 unary, ok := outbound.Unary.(*yarpctest.FakeOutbound) 120 require.True(t, ok, "unary outbound must be fake outbound") 121 122 transports := unary.Transports() 123 require.Equal(t, len(transports), 1, "must have one transport") 124 125 transport, ok := transports[0].(*yarpctest.FakeTransport) 126 require.True(t, ok, "must be a fake transport") 127 assert.Equal(t, transport.NopOption(), ":1234", "transport configured") 128 129 require.NotNil(t, unary.Chooser(), "must have chooser") 130 chooser, ok := unary.Chooser().(*yarpctest.FakePeerChooser) 131 require.True(t, ok, "unary chooser must be a fake peer chooser") 132 require.Equal(t, "*.*", chooser.Nop()) 133 134 dispatcher := yarpc.NewDispatcher(c) 135 assert.NoError(t, dispatcher.Start(), "error starting") 136 assert.NoError(t, dispatcher.Stop(), "error stopping") 137 138 _ = chooser 139 }, 140 }, 141 { 142 desc: "multiple static peers", 143 given: whitespace.Expand(` 144 transports: 145 fake-transport: 146 nop: ":1234" 147 outbounds: 148 their-service: 149 unary: 150 fake-transport: 151 nop: "*.*" 152 fake-list: 153 peers: 154 - 127.0.0.1:8080 155 - 127.0.0.1:8081 156 `), 157 test: func(t *testing.T, c yarpc.Config) { 158 outbound, ok := c.Outbounds["their-service"] 159 require.True(t, ok, "config has outbound") 160 161 require.NotNil(t, outbound.Unary, "must have unary outbound") 162 unary, ok := outbound.Unary.(*yarpctest.FakeOutbound) 163 require.True(t, ok, "unary outbound must be fake outbound") 164 165 transports := unary.Transports() 166 require.Equal(t, len(transports), 1, "must have one transport") 167 168 transport, ok := transports[0].(*yarpctest.FakeTransport) 169 require.True(t, ok, "must be a fake transport") 170 assert.Equal(t, transport.NopOption(), ":1234", "transport configured") 171 172 require.NotNil(t, unary.Chooser(), "must have chooser") 173 chooser, ok := unary.Chooser().(*peer.BoundChooser) 174 require.True(t, ok, "unary chooser must be a bound chooser") 175 176 updater, ok := chooser.Updater().(*peer.PeersUpdater) 177 require.True(t, ok, "updater is a static peer list updater") 178 179 list, ok := chooser.ChooserList().(*yarpctest.FakePeerList) 180 require.True(t, ok, "list is a fake peer list") 181 182 dispatcher := yarpc.NewDispatcher(c) 183 assert.NoError(t, dispatcher.Start(), "error starting") 184 assert.NoError(t, dispatcher.Stop(), "error stopping") 185 186 _ = updater 187 _ = list 188 }, 189 }, 190 { 191 desc: "peer chooser preset", 192 given: whitespace.Expand(` 193 transports: 194 fake-transport: 195 nop: ":1234" 196 outbounds: 197 their-service: 198 unary: 199 fake-transport: 200 nop: "*.*" 201 with: fake-preset 202 `), 203 test: func(t *testing.T, c yarpc.Config) { 204 outbound, ok := c.Outbounds["their-service"] 205 require.True(t, ok, "config has outbound") 206 207 require.NotNil(t, outbound.Unary, "must have unary outbound") 208 unary, ok := outbound.Unary.(*yarpctest.FakeOutbound) 209 require.True(t, ok, "unary outbound must be fake outbound") 210 211 transports := unary.Transports() 212 require.Equal(t, len(transports), 1, "must have one transport") 213 214 transport, ok := transports[0].(*yarpctest.FakeTransport) 215 require.True(t, ok, "must be a fake transport") 216 assert.Equal(t, transport.NopOption(), ":1234", "transport configured") 217 218 require.NotNil(t, unary.Chooser(), "must have chooser") 219 chooser, ok := unary.Chooser().(*peer.BoundChooser) 220 require.True(t, ok, "unary chooser must be a bound chooser") 221 222 updater, ok := chooser.Updater().(*yarpctest.FakePeerListUpdater) 223 require.True(t, ok, "updater is a fake peer list updater") 224 225 list, ok := chooser.ChooserList().(*yarpctest.FakePeerList) 226 require.True(t, ok, "list is a fake peer list") 227 228 dispatcher := yarpc.NewDispatcher(c) 229 assert.NoError(t, dispatcher.Start(), "error starting") 230 assert.NoError(t, dispatcher.Stop(), "error stopping") 231 232 _ = updater 233 _ = list 234 }, 235 }, 236 { 237 desc: "using a peer list updater plugin", 238 given: whitespace.Expand(` 239 transports: 240 fake-transport: 241 nop: ":1234" 242 outbounds: 243 their-service: 244 unary: 245 fake-transport: 246 nop: "*.*" 247 fake-list: 248 fake-updater: 249 watch: true 250 `), 251 test: func(t *testing.T, c yarpc.Config) { 252 outbound, ok := c.Outbounds["their-service"] 253 require.True(t, ok, "config has outbound") 254 255 require.NotNil(t, outbound.Unary, "must have unary outbound") 256 unary, ok := outbound.Unary.(*yarpctest.FakeOutbound) 257 require.True(t, ok, "unary outbound must be fake outbound") 258 259 transports := unary.Transports() 260 require.Equal(t, len(transports), 1, "must have one transport") 261 262 transport, ok := transports[0].(*yarpctest.FakeTransport) 263 require.True(t, ok, "must be a fake transport") 264 assert.Equal(t, transport.NopOption(), ":1234", "transport configured") 265 266 require.NotNil(t, unary.Chooser(), "must have chooser") 267 chooser, ok := unary.Chooser().(*peer.BoundChooser) 268 require.True(t, ok, "unary chooser must be a bound chooser") 269 270 updater, ok := chooser.Updater().(*yarpctest.FakePeerListUpdater) 271 require.True(t, ok, "updater is a peer list updater") 272 assert.True(t, updater.Watch(), "peer list updater configured to watch") 273 274 list, ok := chooser.ChooserList().(*yarpctest.FakePeerList) 275 require.True(t, ok, "list is a fake peer list") 276 _ = list 277 278 dispatcher := yarpc.NewDispatcher(c) 279 assert.NoError(t, dispatcher.Start(), "error starting") 280 assert.NoError(t, dispatcher.Stop(), "error stopping") 281 }, 282 }, 283 { 284 desc: "use static peers with round robin and exercise choose", 285 given: whitespace.Expand(` 286 outbounds: 287 their-service: 288 unary: 289 fake-transport: 290 round-robin: 291 peers: 292 - 127.0.0.1:8080 293 - 127.0.0.1:8081 294 `), 295 test: func(t *testing.T, c yarpc.Config) { 296 outbound := c.Outbounds["their-service"] 297 unary := outbound.Unary.(*yarpctest.FakeOutbound) 298 transport := unary.Transports()[0].(*yarpctest.FakeTransport) 299 chooser := unary.Chooser().(*peer.BoundChooser) 300 binder := chooser.Updater() 301 list, ok := chooser.ChooserList().(*roundrobin.List) 302 require.True(t, ok, "chooser least pending") 303 _ = list 304 305 // Attempt to choose a peer 306 dispatcher := yarpc.NewDispatcher(c) 307 require.NoError(t, dispatcher.Start(), "error starting dispatcher") 308 defer func() { 309 require.NoError(t, dispatcher.Stop(), "error stopping dispatcher") 310 }() 311 312 require.True(t, transport.IsRunning(), "transport is running") 313 require.True(t, unary.IsRunning(), "outbound is running") 314 require.True(t, list.IsRunning(), "chooser is running") 315 require.True(t, binder.IsRunning(), "binder is running") 316 317 ctx := context.Background() 318 ctx, cancel := context.WithTimeout(ctx, testtime.Second) 319 defer cancel() 320 peer, onFinish, err := chooser.Choose(ctx, nil) 321 require.NoError(t, err, "error choosing peer") 322 defer onFinish(nil) 323 324 expectedPeers := []string{"127.0.0.1:8080", "127.0.0.1:8081"} 325 assert.Contains(t, expectedPeers, peer.Identifier(), "chooses one of the provided peers") 326 }, 327 }, 328 { 329 desc: "use round-robin chooser", 330 given: whitespace.Expand(` 331 outbounds: 332 their-service: 333 unary: 334 fake-transport: 335 round-robin: 336 fake-updater: {} 337 `), 338 test: func(t *testing.T, c yarpc.Config) { 339 outbound := c.Outbounds["their-service"] 340 unary := outbound.Unary.(*yarpctest.FakeOutbound) 341 chooser := unary.Chooser().(*peer.BoundChooser) 342 _, ok := chooser.ChooserList().(*roundrobin.List) 343 require.True(t, ok, "use round robin") 344 }, 345 }, 346 { 347 desc: "use round-robin chooser with capacity", 348 given: whitespace.Expand(` 349 outbounds: 350 their-service: 351 unary: 352 fake-transport: 353 round-robin: 354 capacity: 50 355 fake-updater: {} 356 `), 357 test: func(t *testing.T, c yarpc.Config) { 358 outbound := c.Outbounds["their-service"] 359 unary := outbound.Unary.(*yarpctest.FakeOutbound) 360 chooser := unary.Chooser().(*peer.BoundChooser) 361 _, ok := chooser.ChooserList().(*roundrobin.List) 362 require.True(t, ok, "use round robin") 363 }, 364 }, 365 { 366 desc: "use least-pending chooser", 367 given: whitespace.Expand(` 368 outbounds: 369 their-service: 370 unary: 371 fake-transport: 372 least-pending: 373 fake-updater: {} 374 `), 375 test: func(t *testing.T, c yarpc.Config) { 376 outbound := c.Outbounds["their-service"] 377 unary := outbound.Unary.(*yarpctest.FakeOutbound) 378 chooser := unary.Chooser().(*peer.BoundChooser) 379 list, ok := chooser.ChooserList().(*peerheap.List) 380 require.True(t, ok, "use peer heap") 381 _ = list 382 }, 383 }, 384 { 385 desc: "use fewest-pending-requests chooser", 386 given: whitespace.Expand(` 387 outbounds: 388 their-service: 389 unary: 390 fake-transport: 391 fewest-pending-requests: 392 fake-updater: {} 393 `), 394 test: func(t *testing.T, c yarpc.Config) { 395 outbound := c.Outbounds["their-service"] 396 unary := outbound.Unary.(*yarpctest.FakeOutbound) 397 chooser := unary.Chooser().(*peer.BoundChooser) 398 _, ok := chooser.ChooserList().(*pendingheap.List) 399 require.True(t, ok, "use pending heap") 400 }, 401 }, 402 { 403 desc: "use fewest-pending-requests chooser with capacity", 404 given: whitespace.Expand(` 405 outbounds: 406 their-service: 407 unary: 408 fake-transport: 409 fewest-pending-requests: 410 capacity: 50 411 fake-updater: {} 412 `), 413 test: func(t *testing.T, c yarpc.Config) { 414 outbound := c.Outbounds["their-service"] 415 unary := outbound.Unary.(*yarpctest.FakeOutbound) 416 chooser := unary.Chooser().(*peer.BoundChooser) 417 _, ok := chooser.ChooserList().(*pendingheap.List) 418 require.True(t, ok, "use pending heap") 419 }, 420 }, 421 { 422 desc: "HTTP single peer implied by URL", 423 given: whitespace.Expand(` 424 outbounds: 425 their-service: 426 unary: 427 http: 428 url: "https://127.0.0.1/rpc" 429 `), 430 test: func(t *testing.T, c yarpc.Config) { 431 outbound, ok := c.Outbounds["their-service"] 432 require.True(t, ok, "config has outbound") 433 434 require.NotNil(t, outbound.Unary, "must have unary outbound") 435 unary, ok := outbound.Unary.(*http.Outbound) 436 require.True(t, ok, "unary outbound must be HTTP outbound") 437 438 transports := unary.Transports() 439 require.Equal(t, 1, len(transports), "must have one transport") 440 441 transport, ok := transports[0].(*http.Transport) 442 require.True(t, ok, "must be an HTTP transport") 443 444 require.NotNil(t, unary.Chooser(), "must have chooser") 445 chooser, ok := unary.Chooser().(*peer.Single) 446 require.True(t, ok, "unary chooser must be a single peer chooser") 447 448 dispatcher := yarpc.NewDispatcher(c) 449 assert.NoError(t, dispatcher.Start(), "error starting") 450 assert.NoError(t, dispatcher.Stop(), "error stopping") 451 452 _ = transport 453 _ = chooser 454 }, 455 }, 456 { 457 desc: "HTTP", 458 given: whitespace.Expand(` 459 outbounds: 460 their-service: 461 unary: 462 http: 463 url: "https://service.example.com/rpc" 464 peer: "127.0.0.1" 465 `), 466 test: func(t *testing.T, c yarpc.Config) { 467 outbound, ok := c.Outbounds["their-service"] 468 require.True(t, ok, "config has outbound") 469 470 require.NotNil(t, outbound.Unary, "must have unary outbound") 471 unary, ok := outbound.Unary.(*http.Outbound) 472 require.True(t, ok, "unary outbound must be HTTP outbound") 473 474 transports := unary.Transports() 475 require.Equal(t, 1, len(transports), "must have one transport") 476 477 transport, ok := transports[0].(*http.Transport) 478 require.True(t, ok, "must be an HTTP transport") 479 480 require.NotNil(t, unary.Chooser(), "must have chooser") 481 chooser, ok := unary.Chooser().(*peer.Single) 482 require.True(t, ok, "unary chooser must be a single peer chooser") 483 484 dispatcher := yarpc.NewDispatcher(c) 485 assert.NoError(t, dispatcher.Start(), "error starting") 486 assert.NoError(t, dispatcher.Stop(), "error stopping") 487 488 _ = transport 489 _ = chooser 490 }, 491 }, 492 { 493 desc: "tchannel transport", 494 given: whitespace.Expand(` 495 outbounds: 496 their-service: 497 unary: 498 tchannel: 499 peer: 127.0.0.1:4040 500 `), 501 test: func(t *testing.T, c yarpc.Config) { 502 outbound, ok := c.Outbounds["their-service"] 503 require.True(t, ok, "config has outbound") 504 505 require.NotNil(t, outbound.Unary, "must have unary outbound") 506 unary, ok := outbound.Unary.(*tchannel.Outbound) 507 require.True(t, ok, "unary outbound must be TChannel outbound") 508 509 transports := unary.Transports() 510 require.Equal(t, 1, len(transports), "must have one transport") 511 512 transport, ok := transports[0].(*tchannel.Transport) 513 require.True(t, ok, "must be an TChannel transport") 514 515 require.NotNil(t, unary.Chooser(), "must have chooser") 516 chooser, ok := unary.Chooser().(*peer.Single) 517 require.True(t, ok, "unary chooser must be a single peer chooser") 518 519 dispatcher := yarpc.NewDispatcher(c) 520 assert.NoError(t, dispatcher.Start(), "error starting") 521 assert.NoError(t, dispatcher.Stop(), "error stopping") 522 523 _ = transport 524 _ = chooser 525 }, 526 }, 527 { 528 desc: "invalid peer chooser", 529 given: whitespace.Expand(` 530 outbounds: 531 their-service: 532 unary: 533 fake-transport: 534 bogus-list: {} 535 `), 536 wantErr: []string{ 537 `failed to configure unary outbound for "their-service": `, 538 `no recognized peer list or chooser "bogus-list"`, 539 `need one of`, 540 `fake-list`, 541 `least-pending`, 542 `round-robin`, 543 }, 544 }, 545 { 546 desc: "invalid peer list", 547 given: whitespace.Expand(` 548 outbounds: 549 their-service: 550 unary: 551 fake-transport: 552 bogus-list: 553 fake-updater: {} 554 `), 555 wantErr: []string{ 556 `failed to configure unary outbound for "their-service": `, 557 `no recognized peer list or chooser "bogus-list"`, 558 `need one of`, 559 `fake-list`, 560 `least-pending`, 561 `round-robin`, 562 }, 563 }, 564 { 565 desc: "invalid peer chooser preset", 566 given: whitespace.Expand(` 567 outbounds: 568 their-service: 569 unary: 570 fake-transport: 571 with: bogus 572 `), 573 wantErr: []string{ 574 `failed to configure unary outbound for "their-service": `, 575 `no recognized peer chooser preset "bogus"`, 576 `need one of`, 577 `fake`, 578 }, 579 }, 580 { 581 desc: "invalid peer chooser decode error", 582 given: whitespace.Expand(` 583 transports: 584 fake-transport: 585 nop: ":1234" 586 outbounds: 587 their-service: 588 unary: 589 fake-transport: 590 nop: "*.*" 591 fake-chooser: 592 invalidValue: test 593 `), 594 wantErr: []string{ 595 `failed to configure unary outbound for "their-service": `, 596 `failed to decode`, 597 }, 598 }, 599 { 600 desc: "invalid peer chooser decode", 601 given: whitespace.Expand(` 602 transports: 603 fake-transport: 604 nop: ":1234" 605 outbounds: 606 their-service: 607 unary: 608 fake-transport: 609 nop: "*.*" 610 fake-chooser: 611 - 127.0.0.1:8080 612 - 127.0.0.1:8081 613 `), 614 wantErr: []string{ 615 `failed to configure unary outbound for "their-service": `, 616 `failed to read attribute "fake-chooser"`, 617 }, 618 }, 619 { 620 desc: "invalid peer list decode", 621 given: whitespace.Expand(` 622 transports: 623 fake-transport: 624 nop: ":1234" 625 outbounds: 626 their-service: 627 unary: 628 fake-transport: 629 nop: "*.*" 630 fake-list: 631 - 127.0.0.1:8080 632 - 127.0.0.1:8081 633 `), 634 wantErr: []string{ 635 `failed to configure unary outbound for "their-service": `, 636 `failed to read attribute "fake-list"`, 637 }, 638 }, 639 { 640 desc: "invalid peer list updater", 641 given: whitespace.Expand(` 642 outbounds: 643 their-service: 644 unary: 645 fake-transport: 646 fake-list: 647 bogus-updater: 10 648 `), 649 wantErr: []string{ 650 `failed to configure unary outbound for "their-service": `, 651 `no recognized peer list updater in config`, 652 `got bogus-updater`, 653 `need one of fake-updater, invalid-updater`, 654 }, 655 }, 656 { 657 desc: "too many peer list updaters", 658 given: whitespace.Expand(` 659 outbounds: 660 their-service: 661 unary: 662 fake-transport: 663 nop: "*.*" 664 fake-list: 665 fake-updater: 666 - 127.0.0.1:8080 667 - 127.0.0.1:8081 668 invalid-updater: 669 - 127.0.0.1:8080 670 - 127.0.0.1:8081 671 `), 672 wantErr: []string{ 673 `failed to configure unary outbound for "their-service": `, 674 `found too many peer list updaters in config: got`, 675 "fake-updater", "invalid-updater", 676 }, 677 }, 678 { 679 desc: "invalid peer list updater decode", 680 given: whitespace.Expand(` 681 transports: 682 fake-transport: 683 nop: ":1234" 684 outbounds: 685 their-service: 686 unary: 687 fake-transport: 688 nop: "*.*" 689 fake-list: 690 fake-updater: 691 - 127.0.0.1:8080 692 - 127.0.0.1:8081 693 `), 694 wantErr: []string{ 695 `failed to configure unary outbound for "their-service": `, 696 `failed to read attribute "fake-updater"`, 697 }, 698 }, 699 { 700 desc: "extraneous config in combination with single peer", 701 given: whitespace.Expand(` 702 outbounds: 703 their-service: 704 unary: 705 fake-transport: 706 peer: a 707 conspicuously: present 708 `), 709 wantErr: []string{ 710 `failed to configure unary outbound for "their-service": `, 711 `unrecognized attributes in outbound config: `, 712 `conspicuously`, 713 `present`, 714 }, 715 }, 716 { 717 desc: "extraneous transport config in combination with list config", 718 given: whitespace.Expand(` 719 outbounds: 720 their-service: 721 unary: 722 fake-transport: 723 conspicuously: present 724 fake-list: 725 peers: 726 - 127.0.0.1:8080 727 - 127.0.0.1:8081 728 `), 729 wantErr: []string{ 730 `failed to configure unary outbound for "their-service": `, 731 `unrecognized attributes in outbound config: `, 732 `conspicuously`, 733 `present`, 734 }, 735 }, 736 { 737 desc: "extraneous config in combination with multiple peers", 738 given: whitespace.Expand(` 739 outbounds: 740 their-service: 741 unary: 742 fake-transport: 743 fake-list: 744 peers: 745 - 127.0.0.1:8080 746 - 127.0.0.1:8081 747 conspicuously: present 748 `), 749 wantErr: []string{ 750 `failed to configure unary outbound for "their-service": `, 751 `has invalid keys:`, 752 `conspicuously`, 753 }, 754 }, 755 { 756 desc: "extraneous config in combination with preset", 757 given: whitespace.Expand(` 758 outbounds: 759 their-service: 760 unary: 761 fake-transport: 762 with: fake-preset 763 conspicuously: present 764 `), 765 wantErr: []string{ 766 `failed to configure unary outbound for "their-service": `, 767 `conspicuously`, 768 `present`, 769 }, 770 }, 771 { 772 desc: "invalid list peers", 773 given: whitespace.Expand(` 774 outbounds: 775 their-service: 776 unary: 777 fake-transport: 778 fake-list: 779 peers: 780 host1: 127.0.0.1:8080 781 host2: 127.0.0.1:8081 782 `), 783 wantErr: []string{ 784 `failed to configure unary outbound for "their-service": `, 785 `failed to read attribute "peers"`, 786 }, 787 }, 788 { 789 desc: "extraneous config in combination with custom updater", 790 given: whitespace.Expand(` 791 outbounds: 792 their-service: 793 unary: 794 fake-transport: 795 fake-list: 796 fake-updater: 797 watch: true 798 conspicuously: present 799 `), 800 wantErr: []string{ 801 `failed to configure unary outbound for "their-service": `, 802 `conspicuously`, 803 }, 804 }, 805 { 806 desc: "missing peer list config", 807 given: whitespace.Expand(` 808 outbounds: 809 their-service: 810 unary: 811 fake-transport: {} 812 `), 813 wantErr: []string{ 814 `failed to configure unary outbound for "their-service": `, 815 `no peer list or chooser provided in config`, 816 `need one of`, 817 `fake-list`, 818 `least-pending`, 819 `round-robin`, 820 }, 821 }, 822 { 823 desc: "invalid peer chooser builder", 824 given: whitespace.Expand(` 825 outbounds: 826 their-service: 827 unary: 828 fake-transport: 829 invalid-chooser: {} 830 `), 831 wantErr: []string{ 832 `failed to configure unary outbound for "their-service": `, 833 `could not create invalid-chooser`, 834 }, 835 }, 836 { 837 desc: "invalid peer list builder", 838 given: whitespace.Expand(` 839 outbounds: 840 their-service: 841 unary: 842 fake-transport: 843 invalid-list: 844 fake-updater: {} 845 `), 846 wantErr: []string{ 847 `failed to configure unary outbound for "their-service": `, 848 `could not create invalid-list`, 849 }, 850 }, 851 { 852 desc: "invalid peer list updater builder", 853 given: whitespace.Expand(` 854 outbounds: 855 their-service: 856 unary: 857 fake-transport: 858 fake-list: 859 invalid-updater: {} 860 `), 861 wantErr: []string{ 862 `failed to configure unary outbound for "their-service": `, 863 `could not create invalid-updater`, 864 }, 865 }, 866 { 867 desc: "invalid oneway peer list config", 868 given: whitespace.Expand(` 869 outbounds: 870 their-service: 871 oneway: 872 fake-transport: 873 fake-list: 874 invalid-updater: {} 875 `), 876 wantErr: []string{ 877 `failed to configure oneway outbound for "their-service": `, 878 `could not create invalid-updater`, 879 }, 880 }, 881 { 882 desc: "invalid stream peer list config", 883 given: whitespace.Expand(` 884 outbounds: 885 their-service: 886 stream: 887 fake-transport: 888 fake-list: 889 invalid-updater: {} 890 `), 891 wantErr: []string{ 892 `failed to configure stream outbound for "their-service": `, 893 `could not create invalid-updater`, 894 }, 895 }, 896 { 897 desc: "interpolation fallback", 898 given: whitespace.Expand(` 899 transports: 900 fake-transport: 901 nop: ":${FIRST_VAR:1234}" 902 outbounds: 903 their-service: 904 unary: 905 fake-transport: 906 nop: "${SECOND_VAR:*.*}" 907 peer: 127.0.0.1:${THIRD_VAR:8080} 908 `), 909 test: func(t *testing.T, c yarpc.Config) { 910 outbound, ok := c.Outbounds["their-service"] 911 require.True(t, ok, "config has outbound") 912 913 require.NotNil(t, outbound.Unary, "must have unary outbound") 914 unary, ok := outbound.Unary.(*yarpctest.FakeOutbound) 915 require.True(t, ok, "unary outbound must be fake outbound") 916 assert.Equal(t, "*.*", unary.NopOption(), "must have configured pattern") 917 918 transports := unary.Transports() 919 require.Equal(t, 1, len(transports), "must have one transport") 920 921 transport, ok := transports[0].(*yarpctest.FakeTransport) 922 require.True(t, ok, "must be a fake transport") 923 assert.Equal(t, ":1234", transport.NopOption(), "transport configured") 924 925 require.NotNil(t, unary.Chooser(), "must have chooser") 926 chooser, ok := unary.Chooser().(*peer.Single) 927 require.True(t, ok, "unary chooser must be a single peer chooser") 928 assert.Equal(t, "127.0.0.1:8080", chooser.Introspect().Peers[0].Identifier, "incorrect peer") 929 930 dispatcher := yarpc.NewDispatcher(c) 931 assert.NoError(t, dispatcher.Start(), "error starting") 932 assert.NoError(t, dispatcher.Stop(), "error stopping") 933 934 _ = chooser 935 }, 936 }, 937 { 938 desc: "interpolation to env var", 939 given: whitespace.Expand(` 940 transports: 941 fake-transport: 942 nop: ":${FIRST_VAR:1234}" 943 outbounds: 944 their-service: 945 unary: 946 fake-transport: 947 nop: "${SECOND_VAR:*.*}" 948 peer: 127.0.0.1:${THIRD_VAR:808-0} 949 `), 950 env: map[string]string{ 951 "FIRST_VAR": "3456", 952 "SECOND_VAR": "A*A", 953 "THIRD_VAR": "9000", 954 }, 955 test: func(t *testing.T, c yarpc.Config) { 956 outbound, ok := c.Outbounds["their-service"] 957 require.True(t, ok, "config has outbound") 958 959 require.NotNil(t, outbound.Unary, "must have unary outbound") 960 unary, ok := outbound.Unary.(*yarpctest.FakeOutbound) 961 require.True(t, ok, "unary outbound must be fake outbound") 962 assert.Equal(t, "A*A", unary.NopOption(), "must have configured pattern") 963 964 transports := unary.Transports() 965 require.Equal(t, 1, len(transports), "must have one transport") 966 967 transport, ok := transports[0].(*yarpctest.FakeTransport) 968 require.True(t, ok, "must be a fake transport") 969 assert.Equal(t, ":3456", transport.NopOption(), "transport configured") 970 971 require.NotNil(t, unary.Chooser(), "must have chooser") 972 chooser, ok := unary.Chooser().(*peer.Single) 973 require.True(t, ok, "unary chooser must be a single peer chooser") 974 assert.Equal(t, "127.0.0.1:9000", chooser.Introspect().Peers[0].Identifier, "incorrect peer") 975 976 dispatcher := yarpc.NewDispatcher(c) 977 assert.NoError(t, dispatcher.Start(), "error starting") 978 assert.NoError(t, dispatcher.Stop(), "error stopping") 979 980 _ = chooser 981 }, 982 }, 983 { 984 desc: "interpolation to env var for chooser and updater", 985 given: whitespace.Expand(` 986 outbounds: 987 their-service: 988 unary: 989 fake-transport: 990 fake-list: 991 nop: "${LIST_VAR:list}" 992 fake-updater: 993 nop: "${UPDATER_VAR:updater}" 994 watch: true 995 `), 996 env: map[string]string{ 997 "LIST_VAR": "envlist", 998 "UPDATER_VAR": "envupdater", 999 }, 1000 test: func(t *testing.T, c yarpc.Config) { 1001 outbound, ok := c.Outbounds["their-service"] 1002 require.True(t, ok, "config has outbound") 1003 1004 require.NotNil(t, outbound.Unary, "must have unary outbound") 1005 unary, ok := outbound.Unary.(*yarpctest.FakeOutbound) 1006 require.True(t, ok, "unary outbound must be fake outbound") 1007 1008 transports := unary.Transports() 1009 require.Equal(t, 1, len(transports), "must have one transport") 1010 1011 require.NotNil(t, unary.Chooser(), "must have chooser") 1012 chooser, ok := unary.Chooser().(*peer.BoundChooser) 1013 require.True(t, ok, "unary chooser must be a bound chooser") 1014 1015 updater, ok := chooser.Updater().(*yarpctest.FakePeerListUpdater) 1016 require.True(t, ok, "updater is a peer list updater") 1017 assert.True(t, updater.Watch(), "peer list updater configured to watch") 1018 assert.Equal(t, "envupdater", updater.Nop(), "did not properly interpolate variables for peer updater") 1019 1020 list, ok := chooser.ChooserList().(*yarpctest.FakePeerList) 1021 require.True(t, ok, "list is a fake peer list") 1022 assert.Equal(t, "envlist", list.Nop(), "did not properly interpolate variables for peer list") 1023 _ = list 1024 1025 dispatcher := yarpc.NewDispatcher(c) 1026 assert.NoError(t, dispatcher.Start(), "error starting") 1027 assert.NoError(t, dispatcher.Stop(), "error stopping") 1028 1029 _ = chooser 1030 }, 1031 }, 1032 { 1033 desc: "invalid peer list updater", 1034 given: whitespace.Expand(` 1035 outbounds: 1036 their-service: 1037 unary: 1038 fake-transport: 1039 fake-list: 1040 bogus-updater: 10 1041 `), 1042 overrideConfigurator: func() *yarpcconfig.Configurator { 1043 // Unlike yarpctest.NewFakeConfigurator, this does _not_ have 1044 // any registered peer list updaters. 1045 configer := yarpcconfig.New(yarpcconfig.InterpolationResolver(mapVariableResolver(nil))) 1046 configer.MustRegisterTransport(yarpctest.FakeTransportSpec()) 1047 configer.MustRegisterPeerChooser(yarpctest.FakePeerChooserSpec()) 1048 configer.MustRegisterPeerList(yarpctest.FakePeerListSpec()) 1049 configer.MustRegisterTransport(http.TransportSpec()) 1050 configer.MustRegisterTransport(tchannel.TransportSpec(tchannel.Tracer(opentracing.NoopTracer{}))) 1051 configer.MustRegisterPeerList(peerheap.Spec()) 1052 configer.MustRegisterPeerList(pendingheap.Spec()) 1053 configer.MustRegisterPeerList(roundrobin.Spec()) 1054 configer.MustRegisterPeerChooser(invalidPeerChooserSpec()) 1055 configer.MustRegisterPeerList(invalidPeerListSpec()) 1056 //configer.MustRegisterPeerListUpdater(invalidPeerListUpdaterSpec()) //None 1057 return configer 1058 }, 1059 wantErr: []string{ 1060 `failed to configure unary outbound for "their-service": `, 1061 `no recognized peer list updater in config`, 1062 `got bogus-updater`, 1063 `no peer list updaters are registered`, 1064 }, 1065 }, 1066 } 1067 1068 for _, tt := range tests { 1069 t.Run(tt.desc, func(t *testing.T) { 1070 var configer *yarpcconfig.Configurator 1071 if tt.overrideConfigurator != nil { 1072 configer = tt.overrideConfigurator() 1073 } else { 1074 configer = yarpctest.NewFakeConfigurator(yarpcconfig.InterpolationResolver(mapVariableResolver(tt.env))) 1075 configer.MustRegisterTransport(http.TransportSpec()) 1076 configer.MustRegisterTransport(tchannel.TransportSpec(tchannel.Tracer(opentracing.NoopTracer{}))) 1077 configer.MustRegisterPeerList(peerheap.Spec()) 1078 configer.MustRegisterPeerList(pendingheap.Spec()) 1079 configer.MustRegisterPeerList(roundrobin.Spec()) 1080 configer.MustRegisterPeerChooser(invalidPeerChooserSpec()) 1081 configer.MustRegisterPeerList(invalidPeerListSpec()) 1082 configer.MustRegisterPeerListUpdater(invalidPeerListUpdaterSpec()) 1083 } 1084 1085 config, err := configer.LoadConfigFromYAML("fake-service", strings.NewReader(tt.given)) 1086 if err != nil { 1087 if len(tt.wantErr) > 0 { 1088 // Check for every required error substring 1089 for _, wantErr := range tt.wantErr { 1090 require.Contains(t, err.Error(), wantErr, "expected error") 1091 } 1092 } else { 1093 require.NoError(t, err, "error loading config") 1094 } 1095 } else if len(tt.wantErr) > 0 { 1096 require.Error(t, err, "expected error") 1097 } 1098 if tt.test != nil { 1099 tt.test(t, config) 1100 } 1101 }) 1102 } 1103 } 1104 1105 type invalidPeerChooserConfig struct { 1106 } 1107 1108 func buildInvalidPeerChooserConfig(c *invalidPeerChooserConfig, t peerapi.Transport, kit *yarpcconfig.Kit) (peerapi.Chooser, error) { 1109 return nil, errors.New("could not create invalid-chooser") 1110 } 1111 1112 func invalidPeerChooserSpec() yarpcconfig.PeerChooserSpec { 1113 return yarpcconfig.PeerChooserSpec{ 1114 Name: "invalid-chooser", 1115 BuildPeerChooser: buildInvalidPeerChooserConfig, 1116 } 1117 } 1118 1119 type invalidPeerListConfig struct { 1120 } 1121 1122 func buildInvalidPeerListConfig(c *invalidPeerListConfig, t peerapi.Transport, kit *yarpcconfig.Kit) (peerapi.ChooserList, error) { 1123 return nil, errors.New("could not create invalid-list") 1124 } 1125 1126 func invalidPeerListSpec() yarpcconfig.PeerListSpec { 1127 return yarpcconfig.PeerListSpec{ 1128 Name: "invalid-list", 1129 BuildPeerList: buildInvalidPeerListConfig, 1130 } 1131 } 1132 1133 type invalidPeerListUpdaterConfig struct { 1134 } 1135 1136 func buildInvalidPeerListUpdater(c *invalidPeerListUpdaterConfig, kit *yarpcconfig.Kit) (peerapi.Binder, error) { 1137 return nil, errors.New("could not create invalid-updater") 1138 } 1139 1140 func invalidPeerListUpdaterSpec() yarpcconfig.PeerListUpdaterSpec { 1141 return yarpcconfig.PeerListUpdaterSpec{ 1142 Name: "invalid-updater", 1143 BuildPeerListUpdater: buildInvalidPeerListUpdater, 1144 } 1145 } 1146 1147 func TestBuildPeerListInvalidKit(t *testing.T) { 1148 mockCtrl := gomock.NewController(t) 1149 defer mockCtrl.Finish() 1150 1151 // We build a fake InboundConfig that embeds the PeerList. This will let 1152 // us call PeerList.BuildPeerList with the wrong Kit. 1153 type inboundConfig struct { 1154 yarpcconfig.PeerChooser 1155 } 1156 1157 configer := yarpctest.NewFakeConfigurator() 1158 configer.MustRegisterTransport(yarpcconfig.TransportSpec{ 1159 Name: "foo", 1160 BuildTransport: func(struct{}, *yarpcconfig.Kit) (transport.Transport, error) { 1161 return transporttest.NewMockTransport(mockCtrl), nil 1162 }, 1163 BuildInbound: func(cfg *inboundConfig, _ transport.Transport, k *yarpcconfig.Kit) (transport.Inbound, error) { 1164 _, err := cfg.BuildPeerChooser(peertest.NewMockTransport(mockCtrl), hostport.Identify, k) 1165 assert.Error(t, err, "BuildPeerList should fail with an invalid Kit") 1166 return transporttest.NewMockInbound(mockCtrl), err 1167 }, 1168 }) 1169 1170 _, err := configer.LoadConfig("myservice", map[string]interface{}{ 1171 "inbounds": map[string]interface{}{ 1172 "foo": map[string]interface{}{"with": "irrelevant"}, 1173 }, 1174 }) 1175 require.Error(t, err, "LoadConfig should fail") 1176 assert.Contains(t, err.Error(), 1177 "invalid Kit: make sure you passed in the same Kit your Build function received") 1178 } 1179 1180 func mapVariableResolver(m map[string]string) interpolate.VariableResolver { 1181 return func(name string) (value string, ok bool) { 1182 value, ok = m[name] 1183 return 1184 } 1185 }