go.uber.org/yarpc@v1.72.1/peer/peerlist/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 peerlist
    22  
    23  import (
    24  	"context"
    25  	"math/rand"
    26  	"testing"
    27  
    28  	"github.com/stretchr/testify/assert"
    29  	"github.com/stretchr/testify/require"
    30  	"go.uber.org/yarpc/api/peer"
    31  	"go.uber.org/yarpc/api/transport"
    32  	"go.uber.org/yarpc/peer/hostport"
    33  	"go.uber.org/yarpc/yarpctest"
    34  )
    35  
    36  const (
    37  	id1 = hostport.PeerIdentifier("1.2.3.4:1234")
    38  	id2 = hostport.PeerIdentifier("4.3.2.1:4321")
    39  	id3 = hostport.PeerIdentifier("1.1.1.1:1111")
    40  )
    41  
    42  func TestValues(t *testing.T) {
    43  	vs := values(map[string]peer.Identifier{})
    44  	assert.Equal(t, []peer.Identifier{}, vs)
    45  
    46  	vs = values(map[string]peer.Identifier{"_": id1, "__": id2})
    47  	assert.Equal(t, 2, len(vs))
    48  	assert.Contains(t, vs, id1)
    49  	assert.Contains(t, vs, id2)
    50  }
    51  
    52  func TestShuffle(t *testing.T) {
    53  	for _, test := range []struct {
    54  		msg  string
    55  		seed int64
    56  		in   []peer.Identifier
    57  		want []peer.Identifier
    58  	}{
    59  		{
    60  			"empty",
    61  			0,
    62  			[]peer.Identifier{},
    63  			[]peer.Identifier{},
    64  		},
    65  		{
    66  			"some",
    67  			0,
    68  			[]peer.Identifier{id1, id2, id3},
    69  			[]peer.Identifier{id2, id3, id1},
    70  		},
    71  		{
    72  			"different seed",
    73  			7,
    74  			[]peer.Identifier{id1, id2, id3},
    75  			[]peer.Identifier{id2, id1, id3},
    76  		},
    77  	} {
    78  		t.Run(test.msg, func(t *testing.T) {
    79  			randSrc := rand.NewSource(test.seed)
    80  			assert.Equal(t, test.want, shuffle(randSrc, test.in))
    81  		})
    82  	}
    83  }
    84  
    85  // most recently added peer list implementation for the test.
    86  type mraList struct {
    87  	mra peer.StatusPeer
    88  	mrr peer.StatusPeer
    89  }
    90  
    91  var _ peer.ListImplementation = (*mraList)(nil)
    92  
    93  func (l *mraList) Add(peer peer.StatusPeer) peer.Subscriber {
    94  	l.mra = peer
    95  	return &mraSub{}
    96  }
    97  
    98  func (l *mraList) Remove(peer peer.StatusPeer, ps peer.Subscriber) {
    99  	l.mrr = peer
   100  }
   101  
   102  func (l *mraList) Choose(ctx context.Context, req *transport.Request) peer.StatusPeer {
   103  	return l.mra
   104  }
   105  
   106  func (l *mraList) Start() error {
   107  	return nil
   108  }
   109  
   110  func (l *mraList) Stop() error {
   111  	return nil
   112  }
   113  
   114  func (l *mraList) IsRunning() bool {
   115  	return true
   116  }
   117  
   118  type mraSub struct {
   119  }
   120  
   121  func (s *mraSub) NotifyStatusChanged(pid peer.Identifier) {
   122  }
   123  
   124  func TestPeerList(t *testing.T) {
   125  	fake := yarpctest.NewFakeTransport(yarpctest.InitialConnectionStatus(peer.Unavailable))
   126  	impl := &mraList{}
   127  	list := New("mra", fake, impl, Capacity(1), NoShuffle(), Seed(0))
   128  
   129  	peers := list.Peers()
   130  	assert.Len(t, peers, 0)
   131  
   132  	assert.NoError(t, list.Update(peer.ListUpdates{
   133  		Additions: []peer.Identifier{
   134  			hostport.Identify("1.1.1.1:4040"),
   135  			hostport.Identify("2.2.2.2:4040"),
   136  		},
   137  		Removals: []peer.Identifier{},
   138  	}))
   139  
   140  	// Invalid updates before start
   141  	assert.Error(t, list.Update(peer.ListUpdates{
   142  		Additions: []peer.Identifier{
   143  			hostport.Identify("1.1.1.1:4040"),
   144  		},
   145  		Removals: []peer.Identifier{
   146  			hostport.Identify("3.3.3.3:4040"),
   147  		},
   148  	}))
   149  
   150  	assert.Equal(t, 0, list.NumAvailable())
   151  	assert.Equal(t, 0, list.NumUnavailable())
   152  	assert.Equal(t, 2, list.NumUninitialized())
   153  	assert.False(t, list.Available(hostport.Identify("2.2.2.2:4040")))
   154  	assert.True(t, list.Uninitialized(hostport.Identify("2.2.2.2:4040")))
   155  
   156  	require.NoError(t, list.Start())
   157  
   158  	// Connect to the peer and simulate a request.
   159  	fake.SimulateConnect(hostport.Identify("2.2.2.2:4040"))
   160  	assert.Equal(t, 1, list.NumAvailable())
   161  	assert.Equal(t, 1, list.NumUnavailable())
   162  	assert.Equal(t, 0, list.NumUninitialized())
   163  	assert.True(t, list.Available(hostport.Identify("2.2.2.2:4040")))
   164  	assert.False(t, list.Uninitialized(hostport.Identify("2.2.2.2:4040")))
   165  	peers = list.Peers()
   166  	assert.Len(t, peers, 2)
   167  	p, onFinish, err := list.Choose(context.Background(), &transport.Request{})
   168  	assert.Equal(t, "2.2.2.2:4040", p.Identifier())
   169  	require.NoError(t, err)
   170  	onFinish(nil)
   171  
   172  	// Simulate a second connection and request.
   173  	fake.SimulateConnect(hostport.Identify("1.1.1.1:4040"))
   174  	assert.Equal(t, 2, list.NumAvailable())
   175  	assert.Equal(t, 0, list.NumUnavailable())
   176  	assert.Equal(t, 0, list.NumUninitialized())
   177  	peers = list.Peers()
   178  	assert.Len(t, peers, 2)
   179  	p, onFinish, err = list.Choose(context.Background(), &transport.Request{})
   180  	assert.Equal(t, "1.1.1.1:4040", p.Identifier())
   181  	require.NoError(t, err)
   182  	onFinish(nil)
   183  
   184  	fake.SimulateDisconnect(hostport.Identify("2.2.2.2:4040"))
   185  	assert.Equal(t, "2.2.2.2:4040", impl.mrr.Identifier())
   186  
   187  	assert.NoError(t, list.Update(peer.ListUpdates{
   188  		Additions: []peer.Identifier{
   189  			hostport.Identify("3.3.3.3:4040"),
   190  		},
   191  		Removals: []peer.Identifier{
   192  			hostport.Identify("2.2.2.2:4040"),
   193  		},
   194  	}))
   195  
   196  	// Invalid updates
   197  	assert.Error(t, list.Update(peer.ListUpdates{
   198  		Additions: []peer.Identifier{
   199  			hostport.Identify("3.3.3.3:4040"),
   200  		},
   201  		Removals: []peer.Identifier{
   202  			hostport.Identify("4.4.4.4:4040"),
   203  		},
   204  	}))
   205  
   206  	require.NoError(t, list.Stop())
   207  
   208  	// Invalid updates, after stop
   209  	assert.Error(t, list.Update(peer.ListUpdates{
   210  		Additions: []peer.Identifier{
   211  			hostport.Identify("3.3.3.3:4040"),
   212  		},
   213  		Removals: []peer.Identifier{
   214  			hostport.Identify("4.4.4.4:4040"),
   215  		},
   216  	}))
   217  
   218  	assert.NoError(t, list.Update(peer.ListUpdates{
   219  		Additions: []peer.Identifier{},
   220  		Removals: []peer.Identifier{
   221  			hostport.Identify("3.3.3.3:4040"),
   222  		},
   223  	}))
   224  }