go.uber.org/yarpc@v1.72.1/peer/pendingheap/list_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 pendingheap 22 23 import ( 24 "context" 25 "fmt" 26 "net" 27 "sort" 28 "strings" 29 "testing" 30 "time" 31 32 "github.com/golang/mock/gomock" 33 "github.com/stretchr/testify/assert" 34 "github.com/stretchr/testify/require" 35 "go.uber.org/multierr" 36 "go.uber.org/yarpc" 37 "go.uber.org/yarpc/api/peer" 38 . "go.uber.org/yarpc/api/peer/peertest" 39 "go.uber.org/yarpc/api/transport" 40 "go.uber.org/yarpc/internal/testtime" 41 "go.uber.org/yarpc/internal/whitespace" 42 "go.uber.org/yarpc/transport/http" 43 "go.uber.org/yarpc/yarpcconfig" 44 "go.uber.org/yarpc/yarpcerrors" 45 "go.uber.org/zap/zaptest" 46 ) 47 48 func newNotRunningError(err string) error { 49 return yarpcerrors.FailedPreconditionErrorf(`"fewest-pending-requests" peer list is not running: %s`, err) 50 } 51 52 // InsertionOrder is a test option that yields control over random insertion 53 // ordering. Each number corresponds to the position to swap the newly inserted 54 // peer's 'last' value. 55 // 56 // The function MUST return a number in [0, numPeers) 57 func InsertionOrder(f func(numPeers int) int) ListOption { 58 return func(c *listConfig) { 59 c.nextRand = f 60 } 61 } 62 63 // DisableRandomInsertion disables random insertions. 64 func DisableRandomInsertion() ListOption { 65 // avoid swaps by always returning the last index 66 return InsertionOrder(func(numPeers int) int { return numPeers - 1 }) 67 } 68 69 func nextRandFromSlice(indicies []int) func(int) int { 70 i := -1 71 return func(_ int) int { 72 i++ 73 return indicies[i] 74 } 75 } 76 77 func TestPeerHeapList(t *testing.T) { 78 type testStruct struct { 79 msg string 80 81 // nextRand is used with the InsertionOrder(...) option. If nil, this defaults 82 // to DisableRandomInsertion() 83 nextRand func(int) int 84 85 // PeerIDs that will be returned from the transport's OnRetain with "Available" status 86 retainedAvailablePeerIDs []string 87 88 // PeerIDs that will be returned from the transport's OnRetain with "Unavailable" status 89 retainedUnavailablePeerIDs []string 90 91 // PeerIDs that will be released from the transport 92 releasedPeerIDs []string 93 94 // PeerIDs that will return "retainErr" from the transport's OnRetain function 95 errRetainedPeerIDs []string 96 retainErr error 97 98 // PeerIDs that will return "releaseErr" from the transport's OnRelease function 99 errReleasedPeerIDs []string 100 releaseErr error 101 102 // A list of actions that will be applied on the PeerList 103 peerListActions []PeerListAction 104 105 // PeerIDs expected to be in the PeerList's "Available" list after the actions have been applied 106 expectedAvailablePeers []string 107 108 // PeerIDs expected to be in the PeerList's "Unavailable" list after the actions have been applied 109 expectedUnavailablePeers []string 110 111 // Boolean indicating whether the PeerList is "running" after the actions have been applied 112 expectedRunning bool 113 } 114 tests := []testStruct{ 115 { 116 msg: "setup", 117 retainedAvailablePeerIDs: []string{"1"}, 118 expectedAvailablePeers: []string{"1"}, 119 peerListActions: []PeerListAction{ 120 StartAction{}, 121 UpdateAction{AddedPeerIDs: []string{"1"}}, 122 }, 123 expectedRunning: true, 124 }, 125 { 126 msg: "setup with disconnected", 127 retainedAvailablePeerIDs: []string{"1"}, 128 retainedUnavailablePeerIDs: []string{"2"}, 129 peerListActions: []PeerListAction{ 130 StartAction{}, 131 UpdateAction{AddedPeerIDs: []string{"1", "2"}}, 132 }, 133 expectedAvailablePeers: []string{"1"}, 134 expectedUnavailablePeers: []string{"2"}, 135 expectedRunning: true, 136 }, 137 { 138 msg: "start", 139 retainedAvailablePeerIDs: []string{"1"}, 140 expectedAvailablePeers: []string{"1"}, 141 peerListActions: []PeerListAction{ 142 StartAction{}, 143 UpdateAction{AddedPeerIDs: []string{"1"}}, 144 ChooseAction{ 145 ExpectedPeer: "1", 146 }, 147 }, 148 expectedRunning: true, 149 }, 150 { 151 msg: "start stop", 152 retainedAvailablePeerIDs: []string{"1", "2", "3", "4", "5", "6"}, 153 retainedUnavailablePeerIDs: []string{"7", "8", "9"}, 154 releasedPeerIDs: []string{"1", "2", "3", "4", "5", "6", "7", "8", "9"}, 155 peerListActions: []PeerListAction{ 156 StartAction{}, 157 UpdateAction{AddedPeerIDs: []string{"1", "2", "3", "4", "5", "6", "7", "8", "9"}}, 158 StopAction{}, 159 ChooseAction{ 160 ExpectedErrMsg: "is not running", 161 InputContextTimeout: 10 * time.Millisecond, 162 }, 163 }, 164 expectedRunning: false, 165 }, 166 { 167 msg: "update, start, and choose", 168 retainedAvailablePeerIDs: []string{"1"}, 169 expectedAvailablePeers: []string{"1"}, 170 peerListActions: []PeerListAction{ 171 UpdateAction{AddedPeerIDs: []string{"1"}}, 172 StartAction{}, 173 ChooseAction{ExpectedPeer: "1"}, 174 }, 175 expectedRunning: true, 176 }, 177 { 178 msg: "start many and choose", 179 retainedAvailablePeerIDs: []string{"1", "2", "3", "4", "5", "6"}, 180 expectedAvailablePeers: []string{"1", "2", "3", "4", "5", "6"}, 181 peerListActions: []PeerListAction{ 182 StartAction{}, 183 UpdateAction{AddedPeerIDs: []string{"1", "2", "3", "4", "5", "6"}}, 184 ChooseAction{ExpectedPeer: "1"}, 185 ChooseAction{ExpectedPeer: "2"}, 186 ChooseAction{ExpectedPeer: "3"}, 187 ChooseAction{ExpectedPeer: "4"}, 188 ChooseAction{ExpectedPeer: "5"}, 189 ChooseAction{ExpectedPeer: "6"}, 190 ChooseAction{ExpectedPeer: "1"}, 191 }, 192 expectedRunning: true, 193 }, 194 { 195 msg: "assure start is idempotent", 196 retainedAvailablePeerIDs: []string{"1"}, 197 expectedAvailablePeers: []string{"1"}, 198 peerListActions: []PeerListAction{ 199 StartAction{}, 200 UpdateAction{AddedPeerIDs: []string{"1"}}, 201 StartAction{}, 202 StartAction{}, 203 ChooseAction{ 204 ExpectedPeer: "1", 205 }, 206 }, 207 expectedRunning: true, 208 }, 209 { 210 msg: "stop no start", 211 retainedAvailablePeerIDs: []string{}, 212 releasedPeerIDs: []string{}, 213 peerListActions: []PeerListAction{ 214 StopAction{}, 215 UpdateAction{AddedPeerIDs: []string{"1"}}, 216 }, 217 expectedRunning: false, 218 }, 219 { 220 msg: "update retain error", 221 errRetainedPeerIDs: []string{"1"}, 222 retainErr: peer.ErrInvalidPeerType{}, 223 peerListActions: []PeerListAction{ 224 StartAction{}, 225 UpdateAction{AddedPeerIDs: []string{"1"}, ExpectedErr: peer.ErrInvalidPeerType{}}, 226 }, 227 expectedRunning: true, 228 }, 229 { 230 msg: "update retain multiple errors", 231 retainedAvailablePeerIDs: []string{"2"}, 232 errRetainedPeerIDs: []string{"1", "3"}, 233 retainErr: peer.ErrInvalidPeerType{}, 234 peerListActions: []PeerListAction{ 235 StartAction{}, 236 UpdateAction{ 237 AddedPeerIDs: []string{"1", "2", "3"}, 238 ExpectedErr: multierr.Combine(peer.ErrInvalidPeerType{}, peer.ErrInvalidPeerType{}), 239 }, 240 }, 241 expectedAvailablePeers: []string{"2"}, 242 expectedRunning: true, 243 }, 244 { 245 msg: "start stop release error", 246 retainedAvailablePeerIDs: []string{"1"}, 247 errReleasedPeerIDs: []string{"1"}, 248 releaseErr: peer.ErrTransportHasNoReferenceToPeer{}, 249 peerListActions: []PeerListAction{ 250 StartAction{}, 251 UpdateAction{AddedPeerIDs: []string{"1"}}, 252 StopAction{ 253 ExpectedErr: peer.ErrTransportHasNoReferenceToPeer{}, 254 }, 255 }, 256 expectedRunning: false, 257 }, 258 { 259 msg: "assure stop is idempotent", 260 retainedAvailablePeerIDs: []string{"1"}, 261 errReleasedPeerIDs: []string{"1"}, 262 releaseErr: peer.ErrTransportHasNoReferenceToPeer{}, 263 peerListActions: []PeerListAction{ 264 StartAction{}, 265 UpdateAction{AddedPeerIDs: []string{"1"}}, 266 ConcurrentAction{ 267 Actions: []PeerListAction{ 268 StopAction{ 269 ExpectedErr: peer.ErrTransportHasNoReferenceToPeer{}, 270 }, 271 StopAction{ 272 ExpectedErr: peer.ErrTransportHasNoReferenceToPeer{}, 273 }, 274 StopAction{ 275 ExpectedErr: peer.ErrTransportHasNoReferenceToPeer{}, 276 }, 277 }, 278 }, 279 }, 280 expectedRunning: false, 281 }, 282 { 283 msg: "start stop release multiple errors", 284 retainedAvailablePeerIDs: []string{"1", "2", "3"}, 285 releasedPeerIDs: []string{"2"}, 286 errReleasedPeerIDs: []string{"1", "3"}, 287 releaseErr: peer.ErrTransportHasNoReferenceToPeer{}, 288 peerListActions: []PeerListAction{ 289 StartAction{}, 290 UpdateAction{AddedPeerIDs: []string{"1", "2", "3"}}, 291 StopAction{ 292 ExpectedErr: multierr.Combine( 293 peer.ErrTransportHasNoReferenceToPeer{}, 294 peer.ErrTransportHasNoReferenceToPeer{}, 295 ), 296 }, 297 }, 298 expectedRunning: false, 299 }, 300 { 301 msg: "choose before start", 302 peerListActions: []PeerListAction{ 303 ChooseAction{ 304 ExpectedErr: newNotRunningError("context finished while waiting for instance to start: context deadline exceeded"), 305 InputContextTimeout: 10 * time.Millisecond, 306 }, 307 ChooseAction{ 308 ExpectedErr: newNotRunningError("context finished while waiting for instance to start: context deadline exceeded"), 309 InputContextTimeout: 10 * time.Millisecond, 310 }, 311 }, 312 expectedRunning: false, 313 }, 314 { 315 msg: "update before start", 316 retainedAvailablePeerIDs: []string{"1"}, 317 expectedAvailablePeers: []string{"1"}, 318 peerListActions: []PeerListAction{ 319 ConcurrentAction{ 320 Actions: []PeerListAction{ 321 UpdateAction{AddedPeerIDs: []string{"1"}}, 322 StartAction{}, 323 }, 324 Wait: 20 * time.Millisecond, 325 }, 326 }, 327 expectedRunning: true, 328 }, 329 { 330 msg: "start choose no peers", 331 peerListActions: []PeerListAction{ 332 StartAction{}, 333 ChooseAction{ 334 InputContextTimeout: 20 * time.Millisecond, 335 ExpectedErrMsg: "peer list has no peers", 336 }, 337 }, 338 expectedRunning: true, 339 }, 340 { 341 msg: "start then add", 342 retainedAvailablePeerIDs: []string{"1", "2"}, 343 expectedAvailablePeers: []string{"1", "2"}, 344 peerListActions: []PeerListAction{ 345 StartAction{}, 346 UpdateAction{AddedPeerIDs: []string{"1"}}, 347 UpdateAction{AddedPeerIDs: []string{"2"}}, 348 ChooseAction{ExpectedPeer: "1"}, 349 ChooseAction{ExpectedPeer: "2"}, 350 ChooseAction{ExpectedPeer: "1"}, 351 }, 352 expectedRunning: true, 353 }, 354 { 355 msg: "start remove", 356 retainedAvailablePeerIDs: []string{"1", "2"}, 357 expectedAvailablePeers: []string{"2"}, 358 releasedPeerIDs: []string{"1"}, 359 peerListActions: []PeerListAction{ 360 StartAction{}, 361 UpdateAction{AddedPeerIDs: []string{"1", "2"}}, 362 UpdateAction{RemovedPeerIDs: []string{"1"}}, 363 ChooseAction{ExpectedPeer: "2"}, 364 }, 365 expectedRunning: true, 366 }, 367 { 368 msg: "start add many and remove many", 369 retainedAvailablePeerIDs: []string{"1", "2", "3-r", "4-r", "5-a-r", "6-a-r", "7-a", "8-a"}, 370 releasedPeerIDs: []string{"3-r", "4-r", "5-a-r", "6-a-r"}, 371 expectedAvailablePeers: []string{"1", "2", "7-a", "8-a"}, 372 peerListActions: []PeerListAction{ 373 StartAction{}, 374 UpdateAction{AddedPeerIDs: []string{"1", "2", "3-r", "4-r"}}, 375 UpdateAction{ 376 AddedPeerIDs: []string{"5-a-r", "6-a-r", "7-a", "8-a"}, 377 }, 378 UpdateAction{ 379 RemovedPeerIDs: []string{"5-a-r", "6-a-r", "3-r", "4-r"}, 380 }, 381 ChooseAction{ExpectedPeer: "1"}, 382 ChooseAction{ExpectedPeer: "2"}, 383 ChooseAction{ExpectedPeer: "7-a"}, 384 ChooseAction{ExpectedPeer: "8-a"}, 385 ChooseAction{ExpectedPeer: "1"}, 386 }, 387 expectedRunning: true, 388 }, 389 { 390 msg: "add retain error", 391 retainedAvailablePeerIDs: []string{"1", "2"}, 392 expectedAvailablePeers: []string{"1", "2"}, 393 errRetainedPeerIDs: []string{"3"}, 394 retainErr: peer.ErrInvalidPeerType{}, 395 peerListActions: []PeerListAction{ 396 StartAction{}, 397 UpdateAction{AddedPeerIDs: []string{"1", "2"}}, 398 UpdateAction{ 399 AddedPeerIDs: []string{"3"}, 400 ExpectedErr: peer.ErrInvalidPeerType{}, 401 }, 402 ChooseAction{ExpectedPeer: "1"}, 403 ChooseAction{ExpectedPeer: "2"}, 404 ChooseAction{ExpectedPeer: "1"}, 405 }, 406 expectedRunning: true, 407 }, 408 { 409 msg: "add duplicate peer", 410 retainedAvailablePeerIDs: []string{"1", "2"}, 411 expectedAvailablePeers: []string{"1", "2"}, 412 peerListActions: []PeerListAction{ 413 StartAction{}, 414 UpdateAction{AddedPeerIDs: []string{"1", "2"}}, 415 UpdateAction{ 416 AddedPeerIDs: []string{"2"}, 417 ExpectedErr: peer.ErrPeerAddAlreadyInList("2"), 418 }, 419 ChooseAction{ExpectedPeer: "1"}, 420 ChooseAction{ExpectedPeer: "2"}, 421 ChooseAction{ExpectedPeer: "1"}, 422 }, 423 expectedRunning: true, 424 }, 425 { 426 msg: "remove peer not in list", 427 retainedAvailablePeerIDs: []string{"1", "2"}, 428 expectedAvailablePeers: []string{"1", "2"}, 429 peerListActions: []PeerListAction{ 430 StartAction{}, 431 UpdateAction{AddedPeerIDs: []string{"1", "2"}}, 432 UpdateAction{ 433 RemovedPeerIDs: []string{"3"}, 434 ExpectedErr: peer.ErrPeerRemoveNotInList("3"), 435 }, 436 ChooseAction{ExpectedPeer: "1"}, 437 ChooseAction{ExpectedPeer: "2"}, 438 ChooseAction{ExpectedPeer: "1"}, 439 }, 440 expectedRunning: true, 441 }, 442 { 443 msg: "remove release error", 444 retainedAvailablePeerIDs: []string{"1", "2"}, 445 errReleasedPeerIDs: []string{"2"}, 446 releaseErr: peer.ErrTransportHasNoReferenceToPeer{}, 447 expectedAvailablePeers: []string{"1"}, 448 peerListActions: []PeerListAction{ 449 StartAction{}, 450 UpdateAction{AddedPeerIDs: []string{"1", "2"}}, 451 UpdateAction{ 452 RemovedPeerIDs: []string{"2"}, 453 ExpectedErr: peer.ErrTransportHasNoReferenceToPeer{}, 454 }, 455 ChooseAction{ExpectedPeer: "1"}, 456 ChooseAction{ExpectedPeer: "1"}, 457 }, 458 expectedRunning: true, 459 }, 460 { 461 msg: "block but added too late", 462 retainedAvailablePeerIDs: []string{"1"}, 463 expectedAvailablePeers: []string{"1"}, 464 peerListActions: []PeerListAction{ 465 StartAction{}, 466 ConcurrentAction{ 467 Actions: []PeerListAction{ 468 ChooseAction{ 469 InputContextTimeout: 10 * time.Millisecond, 470 ExpectedErrMsg: "peer list has no peers", 471 }, 472 UpdateAction{AddedPeerIDs: []string{"1"}}, 473 }, 474 Wait: 20 * time.Millisecond, 475 }, 476 ChooseAction{ExpectedPeer: "1"}, 477 }, 478 expectedRunning: true, 479 }, 480 { 481 msg: "add unavailable peer", 482 retainedAvailablePeerIDs: []string{"1"}, 483 retainedUnavailablePeerIDs: []string{"2"}, 484 expectedAvailablePeers: []string{"1"}, 485 expectedUnavailablePeers: []string{"2"}, 486 peerListActions: []PeerListAction{ 487 StartAction{}, 488 UpdateAction{AddedPeerIDs: []string{"1"}}, 489 UpdateAction{AddedPeerIDs: []string{"2"}}, 490 ChooseAction{ 491 ExpectedPeer: "1", 492 InputContextTimeout: 20 * time.Millisecond, 493 }, 494 ChooseAction{ 495 ExpectedPeer: "1", 496 InputContextTimeout: 20 * time.Millisecond, 497 }, 498 }, 499 expectedRunning: true, 500 }, 501 { 502 msg: "remove unavailable peer", 503 retainedUnavailablePeerIDs: []string{"1"}, 504 releasedPeerIDs: []string{"1"}, 505 peerListActions: []PeerListAction{ 506 StartAction{}, 507 UpdateAction{AddedPeerIDs: []string{"1"}}, 508 UpdateAction{RemovedPeerIDs: []string{"1"}}, 509 ChooseAction{ 510 InputContextTimeout: 10 * time.Millisecond, 511 ExpectedErrMsg: "peer list has no peers", 512 }, 513 }, 514 expectedRunning: true, 515 }, 516 { 517 msg: "notify peer is now available", 518 retainedUnavailablePeerIDs: []string{"1"}, 519 expectedAvailablePeers: []string{"1"}, 520 peerListActions: []PeerListAction{ 521 StartAction{}, 522 UpdateAction{AddedPeerIDs: []string{"1"}}, 523 ChooseAction{ 524 InputContextTimeout: 10 * time.Millisecond, 525 ExpectedErrMsg: "list has 1 peer but it is not responsive", 526 }, 527 NotifyStatusChangeAction{PeerID: "1", NewConnectionStatus: peer.Available}, 528 ChooseAction{ExpectedPeer: "1"}, 529 }, 530 expectedRunning: true, 531 }, 532 { 533 msg: "notify peer is still available", 534 retainedAvailablePeerIDs: []string{"1"}, 535 expectedAvailablePeers: []string{"1"}, 536 peerListActions: []PeerListAction{ 537 StartAction{}, 538 UpdateAction{AddedPeerIDs: []string{"1"}}, 539 ChooseAction{ExpectedPeer: "1"}, 540 NotifyStatusChangeAction{PeerID: "1", NewConnectionStatus: peer.Available}, 541 ChooseAction{ExpectedPeer: "1"}, 542 }, 543 expectedRunning: true, 544 }, 545 { 546 msg: "notify peer is now unavailable", 547 retainedAvailablePeerIDs: []string{"1"}, 548 expectedUnavailablePeers: []string{"1"}, 549 peerListActions: []PeerListAction{ 550 StartAction{}, 551 UpdateAction{AddedPeerIDs: []string{"1"}}, 552 ChooseAction{ExpectedPeer: "1"}, 553 NotifyStatusChangeAction{PeerID: "1", NewConnectionStatus: peer.Unavailable}, 554 ChooseAction{ 555 InputContextTimeout: 10 * time.Millisecond, 556 ExpectedErrMsg: "peer list has 1 peer but it is not responsive", 557 }, 558 }, 559 expectedRunning: true, 560 }, 561 { 562 msg: "notify peer is still unavailable", 563 retainedUnavailablePeerIDs: []string{"1"}, 564 expectedUnavailablePeers: []string{"1"}, 565 peerListActions: []PeerListAction{ 566 StartAction{}, 567 UpdateAction{AddedPeerIDs: []string{"1"}}, 568 NotifyStatusChangeAction{PeerID: "1", NewConnectionStatus: peer.Unavailable}, 569 ChooseAction{ 570 InputContextTimeout: 10 * time.Millisecond, 571 ExpectedErrMsg: "has 1 peer but it is not responsive", 572 }, 573 }, 574 expectedRunning: true, 575 }, 576 { 577 msg: "notify invalid peer", 578 retainedAvailablePeerIDs: []string{"1"}, 579 releasedPeerIDs: []string{"1"}, 580 peerListActions: []PeerListAction{ 581 StartAction{}, 582 UpdateAction{AddedPeerIDs: []string{"1"}}, 583 UpdateAction{RemovedPeerIDs: []string{"1"}}, 584 NotifyStatusChangeAction{PeerID: "1", NewConnectionStatus: peer.Available}, 585 }, 586 expectedRunning: true, 587 }, 588 { 589 msg: "random insertion", 590 // all scores are equal, degenerating to round-robin behavior 591 // peer ordering is therefore by 'last' 592 nextRand: nextRandFromSlice([]int{ 593 0, // insert p1 at end of list 594 1, // insert p2 at end of list 595 2, // insert p3 at end of list 596 0, // swap p4 with index 0 597 1, // swap p5 with index 1 598 }), 599 retainedAvailablePeerIDs: []string{"1", "2", "3", "4", "5"}, 600 expectedAvailablePeers: []string{"1", "2", "3", "4", "5"}, 601 peerListActions: []PeerListAction{ 602 StartAction{}, 603 UpdateAction{AddedPeerIDs: []string{"1", "2", "3"}}, 604 UpdateAction{AddedPeerIDs: []string{"4", "5"}}, 605 ChooseAction{ExpectedPeer: "4"}, 606 ChooseAction{ExpectedPeer: "5"}, 607 ChooseAction{ExpectedPeer: "3"}, 608 ChooseAction{ExpectedPeer: "1"}, 609 ChooseAction{ExpectedPeer: "2"}, 610 }, 611 expectedRunning: true, 612 }, 613 } 614 615 for _, tt := range tests { 616 t.Run(tt.msg, func(t *testing.T) { 617 mockCtrl := gomock.NewController(t) 618 defer mockCtrl.Finish() 619 620 transport := NewMockTransport(mockCtrl) 621 622 // Healthy Transport Retain/Release 623 peerMap := ExpectPeerRetains( 624 transport, 625 tt.retainedAvailablePeerIDs, 626 tt.retainedUnavailablePeerIDs, 627 ) 628 ExpectPeerReleases(transport, tt.releasedPeerIDs, nil) 629 630 // Unhealthy Transport Retain/Release 631 ExpectPeerRetainsWithError(transport, tt.errRetainedPeerIDs, tt.retainErr) 632 ExpectPeerReleases(transport, tt.errReleasedPeerIDs, tt.releaseErr) 633 634 logger := zaptest.NewLogger(t) 635 636 randOption := DisableRandomInsertion() 637 if tt.nextRand != nil { 638 randOption = InsertionOrder(tt.nextRand) 639 } 640 opts := []ListOption{Capacity(0), noShuffle, randOption, Logger(logger), Seed(0)} 641 642 pl := New(transport, opts...) 643 644 deps := ListActionDeps{ 645 Peers: peerMap, 646 } 647 ApplyPeerListActions(t, pl, tt.peerListActions, deps) 648 649 var availablePeers []string 650 var unavailablePeers []string 651 for _, p := range pl.Peers() { 652 ps := p.Status() 653 if ps.ConnectionStatus == peer.Available { 654 availablePeers = append(availablePeers, p.Identifier()) 655 } else if ps.ConnectionStatus == peer.Unavailable { 656 unavailablePeers = append(unavailablePeers, p.Identifier()) 657 } 658 } 659 sort.Strings(availablePeers) 660 sort.Strings(unavailablePeers) 661 662 assert.Equal(t, tt.expectedAvailablePeers, availablePeers, "incorrect available peers") 663 assert.Equal(t, tt.expectedUnavailablePeers, unavailablePeers, "incorrect unavailable peers") 664 assert.Equal(t, tt.expectedRunning, pl.IsRunning(), "Peer list should match expected final running state") 665 }) 666 } 667 } 668 669 var noShuffle ListOption = func(c *listConfig) { 670 c.shuffle = false 671 } 672 673 func TestFailFastConfig(t *testing.T) { 674 conn, err := net.Listen("tcp", "127.0.0.1:0") 675 require.NoError(t, err) 676 require.NoError(t, conn.Close()) 677 678 serviceName := "test" 679 config := whitespace.Expand(fmt.Sprintf(` 680 outbounds: 681 nowhere: 682 http: 683 fewest-pending-requests: 684 peers: 685 - %q 686 capacity: 10 687 failFast: true 688 `, conn.Addr())) 689 cfgr := yarpcconfig.New() 690 cfgr.MustRegisterTransport(http.TransportSpec()) 691 cfgr.MustRegisterPeerList(Spec()) 692 cfg, err := cfgr.LoadConfigFromYAML(serviceName, strings.NewReader(config)) 693 require.NoError(t, err) 694 695 d := yarpc.NewDispatcher(cfg) 696 d.Start() 697 defer d.Stop() 698 699 ctx, cancel := context.WithTimeout(context.Background(), testtime.Second) 700 defer cancel() 701 702 client := d.MustOutboundConfig("nowhere") 703 _, err = client.Outbounds.Unary.Call(ctx, &transport.Request{ 704 Service: "service", 705 Caller: "caller", 706 Encoding: transport.Encoding("blank"), 707 Procedure: "bogus", 708 Body: strings.NewReader("nada"), 709 }) 710 require.Error(t, err) 711 assert.Contains(t, err.Error(), "peer list has 1 peer but it is not responsive") 712 }