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  }