go.uber.org/yarpc@v1.72.1/peer/abstractlist/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 abstractlist 22 23 import ( 24 "context" 25 "math/rand" 26 "testing" 27 "time" 28 29 "github.com/stretchr/testify/assert" 30 "github.com/stretchr/testify/require" 31 "go.uber.org/yarpc/api/peer" 32 "go.uber.org/yarpc/api/transport" 33 "go.uber.org/yarpc/api/x/introspection" 34 "go.uber.org/yarpc/internal/testtime" 35 "go.uber.org/yarpc/peer/abstractpeer" 36 "go.uber.org/yarpc/peer/hostport" 37 "go.uber.org/yarpc/yarpctest" 38 "go.uber.org/zap" 39 "go.uber.org/zap/zaptest/observer" 40 ) 41 42 const ( 43 id1 = abstractpeer.PeerIdentifier("1.2.3.4:1234") 44 id2 = abstractpeer.PeerIdentifier("4.3.2.1:4321") 45 id3 = abstractpeer.PeerIdentifier("1.1.1.1:1111") 46 ) 47 48 // values returns a slice of the values contained in a map of peers. 49 func values(m map[string]peer.Identifier) []peer.Identifier { 50 vs := make([]peer.Identifier, 0, len(m)) 51 for _, v := range m { 52 vs = append(vs, v) 53 } 54 return vs 55 } 56 func TestValues(t *testing.T) { 57 vs := values(map[string]peer.Identifier{}) 58 assert.Equal(t, []peer.Identifier{}, vs) 59 60 vs = values(map[string]peer.Identifier{"_": id1, "__": id2}) 61 assert.Equal(t, 2, len(vs)) 62 assert.Contains(t, vs, id1) 63 assert.Contains(t, vs, id2) 64 } 65 66 func TestShuffle(t *testing.T) { 67 for _, test := range []struct { 68 msg string 69 seed int64 70 in []peer.Identifier 71 want []peer.Identifier 72 }{ 73 { 74 "empty", 75 0, 76 []peer.Identifier{}, 77 []peer.Identifier{}, 78 }, 79 { 80 "some", 81 0, 82 []peer.Identifier{id1, id2, id3}, 83 []peer.Identifier{id2, id3, id1}, 84 }, 85 { 86 "different seed", 87 7, 88 []peer.Identifier{id1, id2, id3}, 89 []peer.Identifier{id2, id1, id3}, 90 }, 91 } { 92 t.Run(test.msg, func(t *testing.T) { 93 randSrc := rand.NewSource(test.seed) 94 assert.Equal(t, test.want, shuffle(randSrc, test.in)) 95 }) 96 } 97 } 98 99 // most recently added peer list implementation for the test. 100 type mraList struct { 101 mra peer.StatusPeer 102 mrr peer.StatusPeer 103 } 104 105 var _ Implementation = (*mraList)(nil) 106 107 func (l *mraList) Add(peer peer.StatusPeer, pid peer.Identifier) Subscriber { 108 l.mra = peer 109 return &mraSub{} 110 } 111 112 func (l *mraList) Remove(peer peer.StatusPeer, pid peer.Identifier, ps Subscriber) { 113 l.mra = nil 114 l.mrr = peer 115 } 116 117 func (l *mraList) Choose(req *transport.Request) peer.StatusPeer { 118 return l.mra 119 } 120 121 func (l *mraList) Start() error { 122 return nil 123 } 124 125 func (l *mraList) Stop() error { 126 return nil 127 } 128 129 func (l *mraList) IsRunning() bool { 130 return true 131 } 132 133 type mraSub struct{} 134 135 func (s *mraSub) UpdatePendingRequestCount(int) {} 136 137 func TestPeerList(t *testing.T) { 138 fake := yarpctest.NewFakeTransport(yarpctest.InitialConnectionStatus(peer.Unavailable)) 139 impl := &mraList{} 140 core, log := observer.New(zap.DebugLevel) 141 logger := zap.New(core) 142 list := New("mra", fake, impl, Capacity(1), NoShuffle(), Seed(0), Logger(logger)) 143 144 ctx, cancel := context.WithTimeout(context.Background(), testtime.Second) 145 defer cancel() 146 147 peers := list.Peers() 148 assert.Len(t, peers, 0) 149 150 assert.NoError(t, list.Update(peer.ListUpdates{ 151 Additions: []peer.Identifier{ 152 abstractpeer.Identify("1.1.1.1:4040"), 153 abstractpeer.Identify("2.2.2.2:4040"), 154 }, 155 Removals: []peer.Identifier{}, 156 })) 157 158 { 159 entries := log.FilterMessage("peer list update").AllUntimed() 160 require.Len(t, entries, 1) 161 assert.Equal(t, map[string]interface{}{ 162 "additions": int64(2), 163 "removals": int64(0), 164 }, entries[0].ContextMap()) 165 } 166 167 // Invalid updates before start 168 assert.Error(t, list.Update(peer.ListUpdates{ 169 Additions: []peer.Identifier{ 170 abstractpeer.Identify("1.1.1.1:4040"), 171 }, 172 Removals: []peer.Identifier{ 173 abstractpeer.Identify("3.3.3.3:4040"), 174 }, 175 })) 176 177 assert.Equal(t, 0, list.NumAvailable()) 178 assert.Equal(t, 0, list.NumUnavailable()) 179 assert.Equal(t, 2, list.NumUninitialized()) 180 assert.False(t, list.Available(abstractpeer.Identify("2.2.2.2:4040"))) 181 assert.True(t, list.Uninitialized(abstractpeer.Identify("2.2.2.2:4040"))) 182 183 require.NoError(t, list.Start()) 184 185 // Connect to the peer and simulate a request. 186 fake.SimulateConnect(abstractpeer.Identify("2.2.2.2:4040")) 187 assert.Equal(t, 1, list.NumAvailable()) 188 assert.Equal(t, 1, list.NumUnavailable()) 189 assert.Equal(t, 0, list.NumUninitialized()) 190 assert.True(t, list.Available(abstractpeer.Identify("2.2.2.2:4040"))) 191 assert.False(t, list.Uninitialized(abstractpeer.Identify("2.2.2.2:4040"))) 192 peers = list.Peers() 193 assert.Len(t, peers, 2) 194 p, onFinish, err := list.Choose(ctx, &transport.Request{}) 195 require.NoError(t, err) 196 assert.Equal(t, "2.2.2.2:4040", p.Identifier()) 197 require.NoError(t, err) 198 onFinish(nil) 199 200 // Simulate a second connection and request. 201 fake.SimulateConnect(abstractpeer.Identify("1.1.1.1:4040")) 202 assert.Equal(t, 2, list.NumAvailable()) 203 assert.Equal(t, 0, list.NumUnavailable()) 204 assert.Equal(t, 0, list.NumUninitialized()) 205 peers = list.Peers() 206 assert.Len(t, peers, 2) 207 p, onFinish, err = list.Choose(ctx, &transport.Request{}) 208 assert.Equal(t, "1.1.1.1:4040", p.Identifier()) 209 require.NoError(t, err) 210 onFinish(nil) 211 212 fake.SimulateDisconnect(abstractpeer.Identify("2.2.2.2:4040")) 213 assert.Equal(t, "2.2.2.2:4040", impl.mrr.Identifier()) 214 215 assert.NoError(t, list.Update(peer.ListUpdates{ 216 Additions: []peer.Identifier{ 217 abstractpeer.Identify("3.3.3.3:4040"), 218 }, 219 Removals: []peer.Identifier{ 220 abstractpeer.Identify("2.2.2.2:4040"), 221 }, 222 })) 223 224 // Invalid updates 225 assert.Error(t, list.Update(peer.ListUpdates{ 226 Additions: []peer.Identifier{ 227 abstractpeer.Identify("3.3.3.3:4040"), 228 }, 229 Removals: []peer.Identifier{ 230 abstractpeer.Identify("4.4.4.4:4040"), 231 }, 232 })) 233 234 require.NoError(t, list.Stop()) 235 236 // Invalid updates, after stop 237 assert.Error(t, list.Update(peer.ListUpdates{ 238 Additions: []peer.Identifier{ 239 abstractpeer.Identify("3.3.3.3:4040"), 240 }, 241 Removals: []peer.Identifier{ 242 abstractpeer.Identify("4.4.4.4:4040"), 243 }, 244 })) 245 246 assert.NoError(t, list.Update(peer.ListUpdates{ 247 Additions: []peer.Identifier{}, 248 Removals: []peer.Identifier{ 249 abstractpeer.Identify("3.3.3.3:4040"), 250 }, 251 })) 252 } 253 254 func TestFailWait(t *testing.T) { 255 fake := yarpctest.NewFakeTransport(yarpctest.InitialConnectionStatus(peer.Available)) 256 impl := &mraList{} 257 list := New("mra", fake, impl) 258 259 require.NoError(t, list.Start()) 260 261 // This case induces the list to enter the wait loop until a peer is added. 262 263 go func() { 264 time.Sleep(10 * testtime.Millisecond) 265 if err := list.Update(peer.ListUpdates{ 266 Additions: []peer.Identifier{ 267 abstractpeer.Identify("0"), 268 }, 269 }); err != nil { 270 t.Log(err.Error()) 271 t.Fail() 272 } 273 }() 274 275 { 276 ctx, cancel := context.WithTimeout(context.Background(), testtime.Second) 277 defer cancel() 278 279 p, onFinish, err := list.Choose(ctx, &transport.Request{}) 280 require.NoError(t, err) 281 onFinish(nil) 282 283 assert.Equal(t, "0", p.Identifier()) 284 } 285 286 // The following case induces the Choose method to enter the wait loop and 287 // exit with a timeout error. 288 289 fake.SimulateDisconnect(abstractpeer.Identify("0")) 290 291 { 292 ctx, cancel := context.WithTimeout(context.Background(), testtime.Second) 293 defer cancel() 294 295 _, _, err := list.Choose(ctx, &transport.Request{}) 296 require.Error(t, err) 297 assert.Contains(t, err.Error(), "has 1 peer but it is not responsive") 298 } 299 } 300 301 func TestFailFast(t *testing.T) { 302 fake := yarpctest.NewFakeTransport(yarpctest.InitialConnectionStatus(peer.Unavailable)) 303 impl := &mraList{} 304 list := New("mra", fake, impl, FailFast()) 305 306 ctx, cancel := context.WithTimeout(context.Background(), testtime.Second) 307 defer cancel() 308 309 require.NoError(t, list.Start()) 310 311 _, _, err := list.Choose(ctx, &transport.Request{}) 312 require.Error(t, err) 313 assert.Contains(t, err.Error(), "has no peers") 314 } 315 316 func TestIntrospect(t *testing.T) { 317 fake := yarpctest.NewFakeTransport(yarpctest.InitialConnectionStatus(peer.Unavailable)) 318 impl := &mraList{} 319 list := New("mra", fake, impl, FailFast()) 320 321 assert.Equal(t, introspection.ChooserStatus{ 322 Name: "mra", 323 State: "Idle (0/0 available)", 324 Peers: []introspection.PeerStatus{}, 325 }, list.Introspect()) 326 327 require.NoError(t, list.Update(peer.ListUpdates{ 328 Additions: []peer.Identifier{ 329 abstractpeer.Identify("0"), 330 }, 331 })) 332 require.NoError(t, list.Start()) 333 334 assert.Equal(t, introspection.ChooserStatus{ 335 Name: "mra", 336 State: "Running (0/1 available)", 337 Peers: []introspection.PeerStatus{ 338 { 339 Identifier: "0", 340 State: "Unavailable, 0 pending request(s)", 341 }, 342 }, 343 }, list.Introspect()) 344 345 fake.SimulateConnect(abstractpeer.Identify("0")) 346 347 assert.Equal(t, introspection.ChooserStatus{ 348 Name: "mra", 349 State: "Running (1/1 available)", 350 Peers: []introspection.PeerStatus{ 351 { 352 Identifier: "0", 353 State: "Available, 0 pending request(s)", 354 }, 355 }, 356 }, list.Introspect()) 357 358 ctx, cancel := context.WithTimeout(context.Background(), testtime.Millisecond) 359 defer cancel() 360 361 peer, _, err := list.Choose(ctx, nil) 362 require.NoError(t, err) 363 assert.Equal(t, "0", peer.Identifier()) 364 365 assert.Equal(t, introspection.ChooserStatus{ 366 Name: "mra", 367 State: "Running (1/1 available)", 368 Peers: []introspection.PeerStatus{ 369 { 370 Identifier: "0", 371 State: "Available, 1 pending request(s)", 372 }, 373 }, 374 }, list.Introspect()) 375 } 376 377 func TestWaitForNeverStarted(t *testing.T) { 378 fake := yarpctest.NewFakeTransport(yarpctest.InitialConnectionStatus(peer.Unavailable)) 379 impl := &mraList{} 380 list := New("mra", fake, impl, FailFast()) 381 382 ctx, cancel := context.WithTimeout(context.Background(), 0) 383 defer cancel() 384 385 _, _, err := list.Choose(ctx, nil) 386 require.Error(t, err) 387 assert.Contains(t, err.Error(), "context finished while waiting for instance to start: context deadline exceeded") 388 } 389 390 func TestDefaultChooseTimeout(t *testing.T) { 391 fakeTransport := yarpctest.NewFakeTransport() 392 listImplementation := &mraList{} 393 req := &transport.Request{} 394 395 list := New("foo-list", fakeTransport, listImplementation, DefaultChooseTimeout(0)) 396 require.NoError(t, list.Start(), "peer list failed to start") 397 398 err := list.Update(peer.ListUpdates{Additions: []peer.Identifier{ 399 hostport.PeerIdentifier("foo:peer"), 400 }}) 401 require.NoError(t, err, "could not add fake peer to list") 402 403 // no deadline 404 ctx := context.Background() 405 406 _, _, err = list.Choose(ctx, req) 407 assert.NoError(t, err, "expected to choose peer without context deadline") 408 }