go.uber.org/yarpc@v1.72.1/peer/roundrobin/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 roundrobin 22 23 import ( 24 "context" 25 "fmt" 26 "net" 27 "strings" 28 "testing" 29 "time" 30 31 "github.com/golang/mock/gomock" 32 "github.com/stretchr/testify/assert" 33 "github.com/stretchr/testify/require" 34 "go.uber.org/multierr" 35 "go.uber.org/yarpc" 36 "go.uber.org/yarpc/api/peer" 37 . "go.uber.org/yarpc/api/peer/peertest" 38 "go.uber.org/yarpc/api/transport" 39 "go.uber.org/yarpc/api/x/introspection" 40 "go.uber.org/yarpc/internal/testtime" 41 "go.uber.org/yarpc/internal/whitespace" 42 "go.uber.org/yarpc/peer/abstractpeer" 43 "go.uber.org/yarpc/peer/hostport" 44 "go.uber.org/yarpc/transport/http" 45 "go.uber.org/yarpc/yarpcconfig" 46 "go.uber.org/yarpc/yarpcerrors" 47 "go.uber.org/yarpc/yarpctest" 48 "go.uber.org/zap/zaptest" 49 ) 50 51 var _notRunningErrorFormat = `"round-robin" peer list is not running: %s` 52 53 func newNotRunningError(err string) error { 54 return yarpcerrors.FailedPreconditionErrorf(_notRunningErrorFormat, err) 55 } 56 57 func TestRoundRobinList(t *testing.T) { 58 type testStruct struct { 59 msg string 60 61 // PeerIDs that will be returned from the transport's OnRetain with "Available" status 62 retainedAvailablePeerIDs []string 63 64 // PeerIDs that will be returned from the transport's OnRetain with "Unavailable" status 65 retainedUnavailablePeerIDs []string 66 67 // PeerIDs that will be released from the transport 68 releasedPeerIDs []string 69 70 // PeerIDs that will return "retainErr" from the transport's OnRetain function 71 errRetainedPeerIDs []string 72 retainErr error 73 74 // PeerIDs that will return "releaseErr" from the transport's OnRelease function 75 errReleasedPeerIDs []string 76 releaseErr error 77 78 // A list of actions that will be applied on the PeerList 79 peerListActions []PeerListAction 80 81 // PeerIDs expected to be in the PeerList's "Available" list after the actions have been applied 82 expectedAvailablePeers []string 83 84 // PeerIDs expected to be in the PeerList's "Unavailable" list after the actions have been applied 85 expectedUnavailablePeers []string 86 87 // PeerIDs expected to be in the PeerList's "Uninitialized" list after the actions have been applied 88 expectedUninitializedPeers []string 89 90 // Boolean indicating whether the PeerList is "running" after the actions have been applied 91 expectedRunning bool 92 93 // Boolean indicating whether peers should be shuffled 94 shuffle bool 95 } 96 tests := []testStruct{ 97 { 98 msg: "setup", 99 retainedAvailablePeerIDs: []string{"1"}, 100 expectedAvailablePeers: []string{"1"}, 101 peerListActions: []PeerListAction{ 102 StartAction{}, 103 UpdateAction{AddedPeerIDs: []string{"1"}}, 104 }, 105 expectedRunning: true, 106 }, 107 { 108 msg: "setup with disconnected", 109 retainedAvailablePeerIDs: []string{"1"}, 110 retainedUnavailablePeerIDs: []string{"2"}, 111 peerListActions: []PeerListAction{ 112 StartAction{}, 113 UpdateAction{AddedPeerIDs: []string{"1", "2"}}, 114 }, 115 expectedAvailablePeers: []string{"1"}, 116 expectedUnavailablePeers: []string{"2"}, 117 expectedRunning: true, 118 }, 119 { 120 msg: "start", 121 retainedAvailablePeerIDs: []string{"1"}, 122 expectedAvailablePeers: []string{"1"}, 123 peerListActions: []PeerListAction{ 124 StartAction{}, 125 UpdateAction{AddedPeerIDs: []string{"1"}}, 126 ChooseAction{ 127 ExpectedPeer: "1", 128 }, 129 }, 130 expectedRunning: true, 131 }, 132 { 133 msg: "start stop", 134 retainedAvailablePeerIDs: []string{"1", "2", "3", "4", "5", "6"}, 135 retainedUnavailablePeerIDs: []string{"7", "8", "9"}, 136 releasedPeerIDs: []string{"1", "2", "3", "4", "5", "6", "7", "8", "9"}, 137 peerListActions: []PeerListAction{ 138 StartAction{}, 139 UpdateAction{AddedPeerIDs: []string{"1", "2", "3", "4", "5", "6", "7", "8", "9"}}, 140 StopAction{}, 141 ChooseAction{ 142 ExpectedErr: newNotRunningError("could not wait for instance to start running: current state is \"stopped\""), 143 InputContextTimeout: 10 * time.Millisecond, 144 }, 145 }, 146 expectedUninitializedPeers: []string{"1", "2", "3", "4", "5", "6", "7", "8", "9"}, 147 expectedRunning: false, 148 }, 149 { 150 msg: "start many and choose", 151 retainedAvailablePeerIDs: []string{"1", "2", "3", "4", "5", "6"}, 152 expectedAvailablePeers: []string{"1", "2", "3", "4", "5", "6"}, 153 peerListActions: []PeerListAction{ 154 StartAction{}, 155 UpdateAction{AddedPeerIDs: []string{"1", "2", "3", "4", "5", "6"}}, 156 ChooseAction{ExpectedPeer: "1"}, 157 ChooseAction{ExpectedPeer: "2"}, 158 ChooseAction{ExpectedPeer: "3"}, 159 ChooseAction{ExpectedPeer: "4"}, 160 ChooseAction{ExpectedPeer: "5"}, 161 ChooseAction{ExpectedPeer: "6"}, 162 ChooseAction{ExpectedPeer: "1"}, 163 }, 164 expectedRunning: true, 165 }, 166 { 167 msg: "assure start is idempotent", 168 retainedAvailablePeerIDs: []string{"1"}, 169 expectedAvailablePeers: []string{"1"}, 170 peerListActions: []PeerListAction{ 171 StartAction{}, 172 UpdateAction{AddedPeerIDs: []string{"1"}}, 173 StartAction{}, 174 StartAction{}, 175 ChooseAction{ 176 ExpectedPeer: "1", 177 }, 178 }, 179 expectedRunning: true, 180 }, 181 { 182 msg: "stop no start", 183 retainedAvailablePeerIDs: []string{}, 184 releasedPeerIDs: []string{}, 185 peerListActions: []PeerListAction{ 186 StopAction{}, 187 }, 188 expectedRunning: false, 189 }, 190 { 191 msg: "update retain error", 192 errRetainedPeerIDs: []string{"1"}, 193 retainErr: peer.ErrInvalidPeerType{}, 194 peerListActions: []PeerListAction{ 195 StartAction{}, 196 UpdateAction{AddedPeerIDs: []string{"1"}, ExpectedErr: peer.ErrInvalidPeerType{}}, 197 }, 198 expectedRunning: true, 199 }, 200 { 201 msg: "update retain multiple errors", 202 retainedAvailablePeerIDs: []string{"2"}, 203 errRetainedPeerIDs: []string{"1", "3"}, 204 retainErr: peer.ErrInvalidPeerType{}, 205 peerListActions: []PeerListAction{ 206 StartAction{}, 207 UpdateAction{ 208 AddedPeerIDs: []string{"1", "2", "3"}, 209 ExpectedErr: multierr.Combine(peer.ErrInvalidPeerType{}, peer.ErrInvalidPeerType{}), 210 }, 211 }, 212 expectedAvailablePeers: []string{"2"}, 213 expectedRunning: true, 214 }, 215 { 216 msg: "start stop release error", 217 retainedAvailablePeerIDs: []string{"1"}, 218 errReleasedPeerIDs: []string{"1"}, 219 releaseErr: peer.ErrTransportHasNoReferenceToPeer{}, 220 peerListActions: []PeerListAction{ 221 StartAction{}, 222 UpdateAction{AddedPeerIDs: []string{"1"}}, 223 StopAction{ 224 ExpectedErr: peer.ErrTransportHasNoReferenceToPeer{}, 225 }, 226 }, 227 expectedUninitializedPeers: []string{"1"}, 228 expectedRunning: false, 229 }, 230 { 231 msg: "assure stop is idempotent", 232 retainedAvailablePeerIDs: []string{"1"}, 233 errReleasedPeerIDs: []string{"1"}, 234 releaseErr: peer.ErrTransportHasNoReferenceToPeer{}, 235 peerListActions: []PeerListAction{ 236 StartAction{}, 237 UpdateAction{AddedPeerIDs: []string{"1"}}, 238 ConcurrentAction{ 239 Actions: []PeerListAction{ 240 StopAction{ 241 ExpectedErr: peer.ErrTransportHasNoReferenceToPeer{}, 242 }, 243 StopAction{ 244 ExpectedErr: peer.ErrTransportHasNoReferenceToPeer{}, 245 }, 246 StopAction{ 247 ExpectedErr: peer.ErrTransportHasNoReferenceToPeer{}, 248 }, 249 }, 250 }, 251 }, 252 expectedUninitializedPeers: []string{"1"}, 253 expectedRunning: false, 254 }, 255 { 256 msg: "start stop release multiple errors", 257 retainedAvailablePeerIDs: []string{"1", "2", "3"}, 258 releasedPeerIDs: []string{"2"}, 259 errReleasedPeerIDs: []string{"1", "3"}, 260 releaseErr: peer.ErrTransportHasNoReferenceToPeer{}, 261 peerListActions: []PeerListAction{ 262 StartAction{}, 263 UpdateAction{AddedPeerIDs: []string{"1", "2", "3"}}, 264 StopAction{ 265 ExpectedErr: multierr.Combine( 266 peer.ErrTransportHasNoReferenceToPeer{}, 267 peer.ErrTransportHasNoReferenceToPeer{}, 268 ), 269 }, 270 }, 271 expectedUninitializedPeers: []string{"1", "2", "3"}, 272 expectedRunning: false, 273 }, 274 { 275 msg: "choose before start", 276 peerListActions: []PeerListAction{ 277 ChooseAction{ 278 ExpectedErr: newNotRunningError("context finished while waiting for instance to start: context deadline exceeded"), 279 InputContextTimeout: 10 * time.Millisecond, 280 }, 281 ChooseAction{ 282 ExpectedErr: newNotRunningError("context finished while waiting for instance to start: context deadline exceeded"), 283 InputContextTimeout: 10 * time.Millisecond, 284 }, 285 }, 286 expectedRunning: false, 287 }, 288 { 289 msg: "update before start", 290 retainedAvailablePeerIDs: []string{"1"}, 291 expectedAvailablePeers: []string{"1"}, 292 peerListActions: []PeerListAction{ 293 UpdateAction{AddedPeerIDs: []string{"1"}}, 294 StartAction{}, 295 }, 296 expectedRunning: true, 297 }, 298 { 299 msg: "start choose no peers", 300 peerListActions: []PeerListAction{ 301 StartAction{}, 302 ChooseAction{ 303 InputContextTimeout: 20 * time.Millisecond, 304 ExpectedErrMsg: "peer list has no peers", 305 }, 306 }, 307 expectedRunning: true, 308 }, 309 { 310 msg: "start then add", 311 retainedAvailablePeerIDs: []string{"1", "2"}, 312 expectedAvailablePeers: []string{"1", "2"}, 313 peerListActions: []PeerListAction{ 314 StartAction{}, 315 UpdateAction{AddedPeerIDs: []string{"1"}}, 316 UpdateAction{AddedPeerIDs: []string{"2"}}, 317 ChooseAction{ExpectedPeer: "1"}, 318 ChooseAction{ExpectedPeer: "2"}, 319 ChooseAction{ExpectedPeer: "1"}, 320 }, 321 expectedRunning: true, 322 }, 323 { 324 msg: "start remove", 325 retainedAvailablePeerIDs: []string{"1", "2"}, 326 expectedAvailablePeers: []string{"2"}, 327 releasedPeerIDs: []string{"1"}, 328 peerListActions: []PeerListAction{ 329 StartAction{}, 330 UpdateAction{AddedPeerIDs: []string{"1", "2"}}, 331 UpdateAction{RemovedPeerIDs: []string{"1"}}, 332 ChooseAction{ExpectedPeer: "2"}, 333 }, 334 expectedRunning: true, 335 }, 336 { 337 msg: "start add many and remove many", 338 retainedAvailablePeerIDs: []string{"1", "2", "3-r", "4-r", "5-a-r", "6-a-r", "7-a", "8-a"}, 339 releasedPeerIDs: []string{"3-r", "4-r", "5-a-r", "6-a-r"}, 340 expectedAvailablePeers: []string{"1", "2", "7-a", "8-a"}, 341 peerListActions: []PeerListAction{ 342 StartAction{}, 343 UpdateAction{AddedPeerIDs: []string{"1", "2", "3-r", "4-r"}}, 344 UpdateAction{ 345 AddedPeerIDs: []string{"5-a-r", "6-a-r", "7-a", "8-a"}, 346 }, 347 UpdateAction{ 348 RemovedPeerIDs: []string{"5-a-r", "6-a-r", "3-r", "4-r"}, 349 }, 350 ChooseAction{ExpectedPeer: "1"}, 351 ChooseAction{ExpectedPeer: "2"}, 352 ChooseAction{ExpectedPeer: "7-a"}, 353 ChooseAction{ExpectedPeer: "8-a"}, 354 ChooseAction{ExpectedPeer: "1"}, 355 }, 356 expectedRunning: true, 357 }, 358 { 359 msg: "start add many and remove many with shuffle", 360 retainedAvailablePeerIDs: []string{"1", "2", "3-r", "4-r", "5-a-r", "6-a-r", "7-a", "8-a"}, 361 releasedPeerIDs: []string{"3-r", "4-r", "5-a-r", "6-a-r"}, 362 expectedAvailablePeers: []string{"1", "2", "7-a", "8-a"}, 363 peerListActions: []PeerListAction{ 364 StartAction{}, 365 UpdateAction{AddedPeerIDs: []string{"1", "2", "3-r", "4-r"}}, 366 UpdateAction{ 367 AddedPeerIDs: []string{"5-a-r", "6-a-r", "7-a", "8-a"}, 368 }, 369 UpdateAction{ 370 RemovedPeerIDs: []string{"5-a-r", "6-a-r", "3-r", "4-r"}, 371 }, 372 ChooseAction{ExpectedPeer: "2"}, 373 ChooseAction{ExpectedPeer: "1"}, 374 ChooseAction{ExpectedPeer: "8-a"}, 375 ChooseAction{ExpectedPeer: "7-a"}, 376 ChooseAction{ExpectedPeer: "2"}, 377 }, 378 expectedRunning: true, 379 shuffle: true, 380 }, 381 { 382 msg: "add retain error", 383 retainedAvailablePeerIDs: []string{"1", "2"}, 384 expectedAvailablePeers: []string{"1", "2"}, 385 errRetainedPeerIDs: []string{"3"}, 386 retainErr: peer.ErrInvalidPeerType{}, 387 peerListActions: []PeerListAction{ 388 StartAction{}, 389 UpdateAction{AddedPeerIDs: []string{"1", "2"}}, 390 UpdateAction{ 391 AddedPeerIDs: []string{"3"}, 392 ExpectedErr: peer.ErrInvalidPeerType{}, 393 }, 394 ChooseAction{ExpectedPeer: "1"}, 395 ChooseAction{ExpectedPeer: "2"}, 396 ChooseAction{ExpectedPeer: "1"}, 397 }, 398 expectedRunning: true, 399 }, 400 { 401 msg: "add duplicate peer", 402 retainedAvailablePeerIDs: []string{"1", "2"}, 403 expectedAvailablePeers: []string{"1", "2"}, 404 peerListActions: []PeerListAction{ 405 StartAction{}, 406 UpdateAction{AddedPeerIDs: []string{"1", "2"}}, 407 UpdateAction{ 408 AddedPeerIDs: []string{"2"}, 409 ExpectedErr: peer.ErrPeerAddAlreadyInList("2"), 410 }, 411 ChooseAction{ExpectedPeer: "1"}, 412 ChooseAction{ExpectedPeer: "2"}, 413 ChooseAction{ExpectedPeer: "1"}, 414 }, 415 expectedRunning: true, 416 }, 417 { 418 msg: "remove peer not in list", 419 retainedAvailablePeerIDs: []string{"1", "2"}, 420 expectedAvailablePeers: []string{"1", "2"}, 421 peerListActions: []PeerListAction{ 422 StartAction{}, 423 UpdateAction{AddedPeerIDs: []string{"1", "2"}}, 424 UpdateAction{ 425 RemovedPeerIDs: []string{"3"}, 426 ExpectedErr: peer.ErrPeerRemoveNotInList("3"), 427 }, 428 ChooseAction{ExpectedPeer: "1"}, 429 ChooseAction{ExpectedPeer: "2"}, 430 ChooseAction{ExpectedPeer: "1"}, 431 }, 432 expectedRunning: true, 433 }, 434 { 435 msg: "remove release error", 436 retainedAvailablePeerIDs: []string{"1", "2"}, 437 errReleasedPeerIDs: []string{"2"}, 438 releaseErr: peer.ErrTransportHasNoReferenceToPeer{}, 439 expectedAvailablePeers: []string{"1"}, 440 peerListActions: []PeerListAction{ 441 StartAction{}, 442 UpdateAction{AddedPeerIDs: []string{"1", "2"}}, 443 UpdateAction{ 444 RemovedPeerIDs: []string{"2"}, 445 ExpectedErr: peer.ErrTransportHasNoReferenceToPeer{}, 446 }, 447 ChooseAction{ExpectedPeer: "1"}, 448 ChooseAction{ExpectedPeer: "1"}, 449 }, 450 expectedRunning: true, 451 }, 452 // Flaky in CI 453 // { 454 // msg: "block until add", 455 // retainedAvailablePeerIDs: []string{"1"}, 456 // expectedAvailablePeers: []string{"1"}, 457 // peerListActions: []PeerListAction{ 458 // StartAction{}, 459 // ConcurrentAction{ 460 // Actions: []PeerListAction{ 461 // ChooseAction{ 462 // InputContextTimeout: 200 * time.Millisecond, 463 // ExpectedPeer: "1", 464 // }, 465 // UpdateAction{AddedPeerIDs: []string{"1"}}, 466 // }, 467 // Wait: 20 * time.Millisecond, 468 // }, 469 // ChooseAction{ExpectedPeer: "1"}, 470 // }, 471 // expectedRunning: true, 472 // }, 473 // { 474 // msg: "multiple blocking until add", 475 // retainedAvailablePeerIDs: []string{"1"}, 476 // expectedAvailablePeers: []string{"1"}, 477 // peerListActions: []PeerListAction{ 478 // StartAction{}, 479 // ConcurrentAction{ 480 // Actions: []PeerListAction{ 481 // ChooseAction{ 482 // InputContextTimeout: 200 * time.Millisecond, 483 // ExpectedPeer: "1", 484 // }, 485 // ChooseAction{ 486 // InputContextTimeout: 200 * time.Millisecond, 487 // ExpectedPeer: "1", 488 // }, 489 // ChooseAction{ 490 // InputContextTimeout: 200 * time.Millisecond, 491 // ExpectedPeer: "1", 492 // }, 493 // UpdateAction{AddedPeerIDs: []string{"1"}}, 494 // }, 495 // Wait: 10 * time.Millisecond, 496 // }, 497 // ChooseAction{ExpectedPeer: "1"}, 498 // }, 499 // expectedRunning: true, 500 // }, 501 { 502 msg: "block but added too late", 503 retainedAvailablePeerIDs: []string{"1"}, 504 expectedAvailablePeers: []string{"1"}, 505 peerListActions: []PeerListAction{ 506 StartAction{}, 507 ConcurrentAction{ 508 Actions: []PeerListAction{ 509 ChooseAction{ 510 InputContextTimeout: 10 * time.Millisecond, 511 ExpectedErrMsg: "peer list has no peers", 512 }, 513 UpdateAction{AddedPeerIDs: []string{"1"}}, 514 }, 515 Wait: 20 * time.Millisecond, 516 }, 517 ChooseAction{ExpectedPeer: "1"}, 518 }, 519 expectedRunning: true, 520 }, 521 // Flaky in CI 522 // { 523 // msg: "block until new peer after removal of only peer", 524 // retainedAvailablePeerIDs: []string{"1", "2"}, 525 // releasedPeerIDs: []string{"1"}, 526 // expectedAvailablePeers: []string{"2"}, 527 // peerListActions: []PeerListAction{ 528 // StartAction{}, 529 // UpdateAction{AddedPeerIDs: []string{"1"}}, 530 // UpdateAction{RemovedPeerIDs: []string{"1"}}, 531 // ConcurrentAction{ 532 // Actions: []PeerListAction{ 533 // ChooseAction{ 534 // InputContextTimeout: 200 * time.Millisecond, 535 // ExpectedPeer: "2", 536 // }, 537 // UpdateAction{AddedPeerIDs: []string{"2"}}, 538 // }, 539 // Wait: 20 * time.Millisecond, 540 // }, 541 // ChooseAction{ExpectedPeer: "2"}, 542 // }, 543 // expectedRunning: true, 544 // }, 545 { 546 msg: "add unavailable peer", 547 retainedAvailablePeerIDs: []string{"1"}, 548 retainedUnavailablePeerIDs: []string{"2"}, 549 expectedAvailablePeers: []string{"1"}, 550 expectedUnavailablePeers: []string{"2"}, 551 peerListActions: []PeerListAction{ 552 StartAction{}, 553 UpdateAction{AddedPeerIDs: []string{"1"}}, 554 UpdateAction{AddedPeerIDs: []string{"2"}}, 555 ChooseAction{ExpectedPeer: "1"}, 556 ChooseAction{ExpectedPeer: "1"}, 557 }, 558 expectedRunning: true, 559 }, 560 { 561 msg: "remove unavailable peer", 562 retainedUnavailablePeerIDs: []string{"1"}, 563 releasedPeerIDs: []string{"1"}, 564 peerListActions: []PeerListAction{ 565 StartAction{}, 566 UpdateAction{AddedPeerIDs: []string{"1"}}, 567 UpdateAction{RemovedPeerIDs: []string{"1"}}, 568 ChooseAction{ 569 InputContextTimeout: 10 * time.Millisecond, 570 ExpectedErrMsg: "has no peers", 571 }, 572 }, 573 expectedRunning: true, 574 }, 575 { 576 msg: "notify peer is now available", 577 retainedUnavailablePeerIDs: []string{"1"}, 578 expectedAvailablePeers: []string{"1"}, 579 peerListActions: []PeerListAction{ 580 StartAction{}, 581 UpdateAction{AddedPeerIDs: []string{"1"}}, 582 ChooseAction{ 583 InputContextTimeout: 10 * time.Millisecond, 584 ExpectedErrMsg: "has 1 peer but it is not responsive", 585 }, 586 NotifyStatusChangeAction{PeerID: "1", NewConnectionStatus: peer.Available}, 587 ChooseAction{ExpectedPeer: "1"}, 588 }, 589 expectedRunning: true, 590 }, 591 { 592 msg: "notify peer is still available", 593 retainedAvailablePeerIDs: []string{"1"}, 594 expectedAvailablePeers: []string{"1"}, 595 peerListActions: []PeerListAction{ 596 StartAction{}, 597 UpdateAction{AddedPeerIDs: []string{"1"}}, 598 ChooseAction{ExpectedPeer: "1"}, 599 NotifyStatusChangeAction{PeerID: "1", NewConnectionStatus: peer.Available}, 600 ChooseAction{ExpectedPeer: "1"}, 601 }, 602 expectedRunning: true, 603 }, 604 { 605 msg: "notify peer is now unavailable", 606 retainedAvailablePeerIDs: []string{"1"}, 607 expectedUnavailablePeers: []string{"1"}, 608 peerListActions: []PeerListAction{ 609 StartAction{}, 610 UpdateAction{AddedPeerIDs: []string{"1"}}, 611 ChooseAction{ExpectedPeer: "1"}, 612 NotifyStatusChangeAction{PeerID: "1", NewConnectionStatus: peer.Unavailable}, 613 ChooseAction{ 614 InputContextTimeout: 10 * time.Millisecond, 615 ExpectedErrMsg: "has 1 peer but it is not responsive", 616 }, 617 }, 618 expectedRunning: true, 619 }, 620 { 621 msg: "notify peer is still unavailable", 622 retainedUnavailablePeerIDs: []string{"1"}, 623 expectedUnavailablePeers: []string{"1"}, 624 peerListActions: []PeerListAction{ 625 StartAction{}, 626 UpdateAction{AddedPeerIDs: []string{"1"}}, 627 NotifyStatusChangeAction{PeerID: "1", NewConnectionStatus: peer.Unavailable}, 628 ChooseAction{ 629 InputContextTimeout: 10 * time.Millisecond, 630 ExpectedErrMsg: "has 1 peer but it is not responsive", 631 }, 632 }, 633 expectedRunning: true, 634 }, 635 { 636 msg: "notify invalid peer", 637 retainedAvailablePeerIDs: []string{"1"}, 638 releasedPeerIDs: []string{"1"}, 639 peerListActions: []PeerListAction{ 640 StartAction{}, 641 UpdateAction{AddedPeerIDs: []string{"1"}}, 642 UpdateAction{RemovedPeerIDs: []string{"1"}}, 643 NotifyStatusChangeAction{PeerID: "1", NewConnectionStatus: peer.Available}, 644 }, 645 expectedRunning: true, 646 }, 647 { 648 // v: Available, u: Unavailable, a: Added, r: Removed 649 msg: "notify peer stress test", 650 retainedAvailablePeerIDs: []string{"1v", "2va", "3vau", "4var", "5vaur"}, 651 retainedUnavailablePeerIDs: []string{"6u", "7ua", "8uav", "9uar", "10uavr"}, 652 releasedPeerIDs: []string{"4var", "5vaur", "9uar", "10uavr"}, 653 expectedAvailablePeers: []string{"1v", "2va", "8uav"}, 654 expectedUnavailablePeers: []string{"3vau", "6u", "7ua"}, 655 peerListActions: []PeerListAction{ 656 StartAction{}, 657 UpdateAction{AddedPeerIDs: []string{"1v", "6u"}}, 658 659 // Added Peers 660 UpdateAction{ 661 AddedPeerIDs: []string{"2va", "3vau", "4var", "5vaur", "7ua", "8uav", "9uar", "10uavr"}, 662 }, 663 664 ChooseMultiAction{ExpectedPeers: []string{"1v", "2va", "3vau", "4var", "5vaur"}}, 665 ChooseMultiAction{ExpectedPeers: []string{"1v", "2va", "3vau", "4var", "5vaur"}}, 666 667 // Change Status to Unavailable 668 NotifyStatusChangeAction{PeerID: "3vau", NewConnectionStatus: peer.Unavailable}, 669 NotifyStatusChangeAction{PeerID: "5vaur", NewConnectionStatus: peer.Unavailable}, 670 671 ChooseMultiAction{ExpectedPeers: []string{"1v", "2va", "4var"}}, 672 ChooseMultiAction{ExpectedPeers: []string{"1v", "2va", "4var"}}, 673 674 // Change Status to Available 675 NotifyStatusChangeAction{PeerID: "8uav", NewConnectionStatus: peer.Available}, 676 NotifyStatusChangeAction{PeerID: "10uavr", NewConnectionStatus: peer.Available}, 677 678 ChooseMultiAction{ExpectedPeers: []string{"1v", "2va", "4var", "8uav", "10uavr"}}, 679 ChooseMultiAction{ExpectedPeers: []string{"1v", "2va", "4var", "8uav", "10uavr"}}, 680 681 // Remove Peers 682 UpdateAction{ 683 RemovedPeerIDs: []string{"4var", "5vaur", "9uar", "10uavr"}, 684 }, 685 686 ChooseMultiAction{ExpectedPeers: []string{"1v", "2va", "8uav"}}, 687 ChooseMultiAction{ExpectedPeers: []string{"1v", "2va", "8uav"}}, 688 }, 689 expectedRunning: true, 690 }, 691 // Flaky in CI 692 // { 693 // msg: "block until notify available", 694 // retainedUnavailablePeerIDs: []string{"1"}, 695 // expectedAvailablePeers: []string{"1"}, 696 // peerListActions: []PeerListAction{ 697 // StartAction{}, 698 // UpdateAction{AddedPeerIDs: []string{"1"}}, 699 // ConcurrentAction{ 700 // Actions: []PeerListAction{ 701 // ChooseAction{ 702 // InputContextTimeout: 200 * time.Millisecond, 703 // ExpectedPeer: "1", 704 // }, 705 // NotifyStatusChangeAction{PeerID: "1", NewConnectionStatus: peer.Available}, 706 // }, 707 // Wait: 20 * time.Millisecond, 708 // }, 709 // ChooseAction{ExpectedPeer: "1"}, 710 // }, 711 // expectedRunning: true, 712 // }, 713 { 714 msg: "update no start", 715 peerListActions: []PeerListAction{ 716 UpdateAction{AddedPeerIDs: []string{"1"}}, 717 UpdateAction{AddedPeerIDs: []string{"2"}}, 718 UpdateAction{RemovedPeerIDs: []string{"1"}}, 719 }, 720 expectedUninitializedPeers: []string{"2"}, 721 expectedRunning: false, 722 }, 723 { 724 msg: "update after stop", 725 peerListActions: []PeerListAction{ 726 StartAction{}, 727 StopAction{}, 728 UpdateAction{AddedPeerIDs: []string{"1"}}, 729 UpdateAction{AddedPeerIDs: []string{"2"}}, 730 UpdateAction{RemovedPeerIDs: []string{"1"}}, 731 }, 732 expectedUninitializedPeers: []string{"2"}, 733 expectedRunning: false, 734 }, 735 { 736 msg: "remove peer not in list before start", 737 peerListActions: []PeerListAction{ 738 UpdateAction{AddedPeerIDs: []string{"1", "2"}}, 739 UpdateAction{ 740 RemovedPeerIDs: []string{"3"}, 741 ExpectedErr: peer.ErrPeerRemoveNotInList("3"), 742 }, 743 }, 744 expectedUninitializedPeers: []string{"1", "2"}, 745 expectedRunning: false, 746 }, 747 { 748 msg: "update before start", 749 retainedAvailablePeerIDs: []string{"1", "2"}, 750 expectedAvailablePeers: []string{"1", "2"}, 751 peerListActions: []PeerListAction{ 752 UpdateAction{AddedPeerIDs: []string{"1", "2"}}, 753 StartAction{}, 754 }, 755 expectedRunning: true, 756 }, 757 { 758 msg: "update before start, and stop", 759 retainedAvailablePeerIDs: []string{"1", "2"}, 760 releasedPeerIDs: []string{"1", "2"}, 761 peerListActions: []PeerListAction{ 762 UpdateAction{AddedPeerIDs: []string{"1", "2"}}, 763 StartAction{}, 764 StopAction{}, 765 }, 766 expectedUninitializedPeers: []string{"1", "2"}, 767 expectedRunning: false, 768 }, 769 { 770 msg: "update before start, and update after stop", 771 retainedAvailablePeerIDs: []string{"1", "2"}, 772 releasedPeerIDs: []string{"1", "2"}, 773 peerListActions: []PeerListAction{ 774 UpdateAction{AddedPeerIDs: []string{"1", "2"}}, 775 StartAction{}, 776 StopAction{}, 777 UpdateAction{AddedPeerIDs: []string{"3"}, RemovedPeerIDs: []string{"1"}}, 778 }, 779 expectedUninitializedPeers: []string{"2", "3"}, 780 expectedRunning: false, 781 }, 782 { 783 msg: "concurrent update and start", 784 retainedAvailablePeerIDs: []string{"1", "2"}, 785 expectedAvailablePeers: []string{"1", "2"}, 786 peerListActions: []PeerListAction{ 787 UpdateAction{AddedPeerIDs: []string{"1"}}, 788 ConcurrentAction{ 789 Actions: []PeerListAction{ 790 StartAction{}, 791 UpdateAction{AddedPeerIDs: []string{"2"}}, 792 StartAction{}, 793 }, 794 }, 795 }, 796 expectedRunning: true, 797 }, 798 { 799 msg: "concurrent update and stop", 800 retainedAvailablePeerIDs: []string{"1", "2"}, 801 releasedPeerIDs: []string{"1", "2"}, 802 peerListActions: []PeerListAction{ 803 StartAction{}, 804 UpdateAction{AddedPeerIDs: []string{"1", "2"}}, 805 ConcurrentAction{ 806 Actions: []PeerListAction{ 807 StopAction{}, 808 UpdateAction{RemovedPeerIDs: []string{"2"}}, 809 StopAction{}, 810 }, 811 }, 812 }, 813 expectedUninitializedPeers: []string{"1"}, 814 expectedRunning: false, 815 }, 816 { 817 msg: "notify before start", 818 peerListActions: []PeerListAction{ 819 UpdateAction{AddedPeerIDs: []string{"1", "2"}}, 820 NotifyStatusChangeAction{PeerID: "1", NewConnectionStatus: peer.Available, Unretained: true}, 821 NotifyStatusChangeAction{PeerID: "2", NewConnectionStatus: peer.Unavailable, Unretained: true}, 822 }, 823 expectedUninitializedPeers: []string{"1", "2"}, 824 expectedRunning: false, 825 }, 826 { 827 msg: "notify after stop", 828 retainedAvailablePeerIDs: []string{"1", "2"}, 829 releasedPeerIDs: []string{"1", "2"}, 830 peerListActions: []PeerListAction{ 831 StartAction{}, 832 UpdateAction{AddedPeerIDs: []string{"1", "2"}}, 833 StopAction{}, 834 NotifyStatusChangeAction{PeerID: "1", NewConnectionStatus: peer.Available}, 835 NotifyStatusChangeAction{PeerID: "2", NewConnectionStatus: peer.Unavailable}, 836 }, 837 expectedUninitializedPeers: []string{"1", "2"}, 838 expectedRunning: false, 839 }, 840 { 841 msg: "start with available and unavailable", 842 retainedAvailablePeerIDs: []string{"1", "2"}, 843 retainedUnavailablePeerIDs: []string{"3", "4"}, 844 expectedAvailablePeers: []string{"1", "2"}, 845 expectedUnavailablePeers: []string{"3", "4"}, 846 peerListActions: []PeerListAction{ 847 UpdateAction{AddedPeerIDs: []string{"1", "2", "3", "4"}}, 848 StartAction{}, 849 }, 850 expectedRunning: true, 851 }, 852 { 853 msg: "stop with available and unavailable", 854 retainedAvailablePeerIDs: []string{"1", "2"}, 855 retainedUnavailablePeerIDs: []string{"3", "4"}, 856 releasedPeerIDs: []string{"1", "2", "3", "4"}, 857 peerListActions: []PeerListAction{ 858 StartAction{}, 859 UpdateAction{AddedPeerIDs: []string{"1", "2", "3", "4"}}, 860 StopAction{}, 861 }, 862 expectedUninitializedPeers: []string{"1", "2", "3", "4"}, 863 expectedRunning: false, 864 }, 865 } 866 867 for _, tt := range tests { 868 t.Run(tt.msg, func(t *testing.T) { 869 mockCtrl := gomock.NewController(t) 870 defer mockCtrl.Finish() 871 872 transport := NewMockTransport(mockCtrl) 873 874 // Healthy Transport Retain/Release 875 peerMap := ExpectPeerRetains( 876 transport, 877 tt.retainedAvailablePeerIDs, 878 tt.retainedUnavailablePeerIDs, 879 ) 880 ExpectPeerReleases(transport, tt.releasedPeerIDs, nil) 881 882 // Unhealthy Transport Retain/Release 883 ExpectPeerRetainsWithError(transport, tt.errRetainedPeerIDs, tt.retainErr) 884 ExpectPeerReleases(transport, tt.errReleasedPeerIDs, tt.releaseErr) 885 886 opts := []ListOption{seed(0), Logger(zaptest.NewLogger(t))} 887 if !tt.shuffle { 888 opts = append(opts, noShuffle) 889 } 890 pl := New(transport, opts...) 891 892 deps := ListActionDeps{ 893 Peers: peerMap, 894 } 895 ApplyPeerListActions(t, pl, tt.peerListActions, deps) 896 897 assert.Equal(t, len(tt.expectedAvailablePeers), pl.list.NumAvailable(), "invalid available peerlist size") 898 for _, expectedRingPeer := range tt.expectedAvailablePeers { 899 ok := pl.list.Available(hostport.PeerIdentifier(expectedRingPeer)) 900 assert.True(t, ok, fmt.Sprintf("expected peer: %s was not in available peerlist", expectedRingPeer)) 901 } 902 903 assert.Equal(t, len(tt.expectedUnavailablePeers), pl.list.NumUnavailable(), "invalid unavailable peerlist size") 904 for _, expectedUnavailablePeer := range tt.expectedUnavailablePeers { 905 ok := !pl.list.Available(hostport.PeerIdentifier(expectedUnavailablePeer)) 906 assert.True(t, ok, fmt.Sprintf("expected peer: %s was not in unavailable peerlist", expectedUnavailablePeer)) 907 } 908 909 assert.Equal(t, len(tt.expectedUninitializedPeers), pl.list.NumUninitialized(), "invalid uninitialized peerlist size") 910 for _, expectedUninitializedPeer := range tt.expectedUninitializedPeers { 911 ok := pl.list.Uninitialized(hostport.PeerIdentifier(expectedUninitializedPeer)) 912 assert.True(t, ok, fmt.Sprintf("expected peer: %s was not in uninitialized peerlist", expectedUninitializedPeer)) 913 } 914 915 assert.Equal(t, tt.expectedRunning, pl.IsRunning(), "List was not in the expected state") 916 }) 917 } 918 } 919 920 func TestIntrospect(t *testing.T) { 921 trans := yarpctest.NewFakeTransport(yarpctest.InitialConnectionStatus(peer.Unavailable)) 922 pl := New(trans, noShuffle) 923 assert.NoError(t, pl.Update(peer.ListUpdates{ 924 Additions: []peer.Identifier{ 925 abstractpeer.Identify("foo"), 926 abstractpeer.Identify("bar"), 927 abstractpeer.Identify("baz"), 928 }, 929 })) 930 require.NoError(t, pl.Start()) 931 932 trans.SimulateConnect(abstractpeer.Identify("bar")) 933 trans.SimulateConnect(abstractpeer.Identify("baz")) 934 935 // Simulate some load. 936 ctx, cancel := context.WithTimeout(context.Background(), testtime.Second) 937 defer cancel() 938 { 939 p, _, err := pl.Choose(ctx, &transport.Request{}) 940 require.NoError(t, err) 941 assert.Equal(t, p.Identifier(), "bar") 942 } 943 { 944 p, _, err := pl.Choose(ctx, &transport.Request{}) 945 require.NoError(t, err) 946 assert.Equal(t, p.Identifier(), "baz") 947 } 948 { 949 p, _, err := pl.Choose(ctx, &transport.Request{}) 950 require.NoError(t, err) 951 assert.Equal(t, p.Identifier(), "bar") 952 } 953 954 chooserStatus := pl.Introspect() 955 assert.Equal(t, "round-robin", chooserStatus.Name) 956 assert.Equal(t, "Running (2/3 available)", chooserStatus.State) 957 958 peerIdentifierToPeerStatus := make(map[string]introspection.PeerStatus, len(chooserStatus.Peers)) 959 for _, peerStatus := range chooserStatus.Peers { 960 peerIdentifierToPeerStatus[peerStatus.Identifier] = peerStatus 961 } 962 checkPeerStatus(t, peerIdentifierToPeerStatus, "foo", "Unavailable, 0 pending request(s)") 963 checkPeerStatus(t, peerIdentifierToPeerStatus, "bar", "Available, 2 pending request(s)") 964 checkPeerStatus(t, peerIdentifierToPeerStatus, "baz", "Available, 1 pending request(s)") 965 } 966 967 func checkPeerStatus( 968 t *testing.T, 969 peerIdentifierToPeerStatus map[string]introspection.PeerStatus, 970 identifier string, 971 expectedState string, 972 ) { 973 peerStatus, ok := peerIdentifierToPeerStatus[identifier] 974 assert.True(t, ok) 975 assert.Equal(t, expectedState, peerStatus.State) 976 } 977 978 var noShuffle ListOption = func(c *listConfig) { 979 c.shuffle = false 980 } 981 982 func seed(seed int64) ListOption { 983 return func(c *listConfig) { 984 c.seed = seed 985 } 986 } 987 988 func TestFailFastConfig(t *testing.T) { 989 conn, err := net.Listen("tcp", "127.0.0.1:0") 990 require.NoError(t, err) 991 require.NoError(t, conn.Close()) 992 993 serviceName := "test" 994 config := whitespace.Expand(fmt.Sprintf(` 995 outbounds: 996 nowhere: 997 http: 998 round-robin: 999 peers: 1000 - %q 1001 capacity: 10 1002 failFast: true 1003 `, conn.Addr())) 1004 cfgr := yarpcconfig.New() 1005 cfgr.MustRegisterTransport(http.TransportSpec()) 1006 cfgr.MustRegisterPeerList(Spec()) 1007 cfg, err := cfgr.LoadConfigFromYAML(serviceName, strings.NewReader(config)) 1008 require.NoError(t, err) 1009 1010 d := yarpc.NewDispatcher(cfg) 1011 d.Start() 1012 defer d.Stop() 1013 1014 ctx, cancel := context.WithTimeout(context.Background(), testtime.Second) 1015 defer cancel() 1016 1017 client := d.MustOutboundConfig("nowhere") 1018 _, err = client.Outbounds.Unary.Call(ctx, &transport.Request{ 1019 Service: "service", 1020 Caller: "caller", 1021 Encoding: transport.Encoding("blank"), 1022 Procedure: "bogus", 1023 Body: strings.NewReader("nada"), 1024 }) 1025 require.Error(t, err) 1026 assert.Contains(t, err.Error(), "has 1 peer but it is not responsive") 1027 }