go.uber.org/yarpc@v1.72.1/transport/http/transport_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 http
    22  
    23  import (
    24  	"context"
    25  	"errors"
    26  	"net"
    27  	"net/http"
    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/yarpc/api/peer"
    35  	. "go.uber.org/yarpc/api/peer/peertest"
    36  	"go.uber.org/yarpc/internal/testtime"
    37  	ypeer "go.uber.org/yarpc/peer"
    38  	"go.uber.org/yarpc/peer/hostport"
    39  )
    40  
    41  // NoJitter is a transport option only available in tests, to disable jitter
    42  // between connection attempts.
    43  func NoJitter() TransportOption {
    44  	return func(options *transportOptions) {
    45  		options.jitter = func(n int64) int64 {
    46  			return n
    47  		}
    48  	}
    49  }
    50  
    51  type peerExpectation struct {
    52  	id          string
    53  	subscribers []string
    54  }
    55  
    56  func createPeerIdentifierMap(ids []string) map[string]peer.Identifier {
    57  	pids := make(map[string]peer.Identifier, len(ids))
    58  	for _, id := range ids {
    59  		pids[id] = &testIdentifier{id}
    60  	}
    61  	return pids
    62  }
    63  
    64  func TestTransport(t *testing.T) {
    65  	type testStruct struct {
    66  		msg string
    67  
    68  		// identifiers defines all the Identifiers that will be used in
    69  		// the actions up from so they can be generated and passed as deps
    70  		identifiers []string
    71  
    72  		// subscriberDefs defines all the Subscribers that will be used in
    73  		// the actions up from so they can be generated and passed as deps
    74  		subscriberDefs []SubscriberDefinition
    75  
    76  		// actions are the actions that will be applied against the transport
    77  		actions []TransportAction
    78  
    79  		// expectedPeers are a list of peers (and those peer's subscribers)
    80  		// that are expected on the transport after the actions
    81  		expectedPeers []peerExpectation
    82  	}
    83  	tests := []testStruct{
    84  		{
    85  			msg:         "one retain",
    86  			identifiers: []string{"i1"},
    87  			subscriberDefs: []SubscriberDefinition{
    88  				{ID: "s1"},
    89  			},
    90  			actions: []TransportAction{
    91  				RetainAction{InputIdentifierID: "i1", InputSubscriberID: "s1", ExpectedPeerID: "i1"},
    92  			},
    93  			expectedPeers: []peerExpectation{
    94  				{id: "i1", subscribers: []string{"s1"}},
    95  			},
    96  		},
    97  		{
    98  			msg:         "one retain one release",
    99  			identifiers: []string{"i1"},
   100  			subscriberDefs: []SubscriberDefinition{
   101  				{ID: "s1"},
   102  			},
   103  			actions: []TransportAction{
   104  				RetainAction{InputIdentifierID: "i1", InputSubscriberID: "s1", ExpectedPeerID: "i1"},
   105  				ReleaseAction{InputIdentifierID: "i1", InputSubscriberID: "s1"},
   106  			},
   107  		},
   108  		{
   109  			msg:         "three retains",
   110  			identifiers: []string{"i1"},
   111  			subscriberDefs: []SubscriberDefinition{
   112  				{ID: "s1"},
   113  				{ID: "s2"},
   114  				{ID: "s3"},
   115  			},
   116  			actions: []TransportAction{
   117  				RetainAction{InputIdentifierID: "i1", InputSubscriberID: "s1", ExpectedPeerID: "i1"},
   118  				RetainAction{InputIdentifierID: "i1", InputSubscriberID: "s2", ExpectedPeerID: "i1"},
   119  				RetainAction{InputIdentifierID: "i1", InputSubscriberID: "s3", ExpectedPeerID: "i1"},
   120  			},
   121  			expectedPeers: []peerExpectation{
   122  				{id: "i1", subscribers: []string{"s1", "s2", "s3"}},
   123  			},
   124  		},
   125  		{
   126  			msg:         "three retains one release",
   127  			identifiers: []string{"i1"},
   128  			subscriberDefs: []SubscriberDefinition{
   129  				{ID: "s1"},
   130  				{ID: "s2r"},
   131  				{ID: "s3"},
   132  			},
   133  			actions: []TransportAction{
   134  				RetainAction{InputIdentifierID: "i1", InputSubscriberID: "s1", ExpectedPeerID: "i1"},
   135  				RetainAction{InputIdentifierID: "i1", InputSubscriberID: "s2r", ExpectedPeerID: "i1"},
   136  				RetainAction{InputIdentifierID: "i1", InputSubscriberID: "s3", ExpectedPeerID: "i1"},
   137  				ReleaseAction{InputIdentifierID: "i1", InputSubscriberID: "s2r"},
   138  			},
   139  			expectedPeers: []peerExpectation{
   140  				{id: "i1", subscribers: []string{"s1", "s3"}},
   141  			},
   142  		},
   143  		{
   144  			msg:         "three retains, three release",
   145  			identifiers: []string{"i1"},
   146  			subscriberDefs: []SubscriberDefinition{
   147  				{ID: "s1"},
   148  				{ID: "s2"},
   149  				{ID: "s3"},
   150  			},
   151  			actions: []TransportAction{
   152  				RetainAction{InputIdentifierID: "i1", InputSubscriberID: "s1", ExpectedPeerID: "i1"},
   153  				RetainAction{InputIdentifierID: "i1", InputSubscriberID: "s2", ExpectedPeerID: "i1"},
   154  				RetainAction{InputIdentifierID: "i1", InputSubscriberID: "s3", ExpectedPeerID: "i1"},
   155  				ReleaseAction{InputIdentifierID: "i1", InputSubscriberID: "s1"},
   156  				ReleaseAction{InputIdentifierID: "i1", InputSubscriberID: "s2"},
   157  				ReleaseAction{InputIdentifierID: "i1", InputSubscriberID: "s3"},
   158  			},
   159  		},
   160  		{
   161  			msg:         "no retains one release",
   162  			identifiers: []string{"i1"},
   163  			subscriberDefs: []SubscriberDefinition{
   164  				{ID: "s1"},
   165  			},
   166  			actions: []TransportAction{
   167  				ReleaseAction{
   168  					InputIdentifierID: "i1",
   169  					InputSubscriberID: "s1",
   170  					ExpectedErrType:   peer.ErrTransportHasNoReferenceToPeer{},
   171  				},
   172  			},
   173  		},
   174  		{
   175  			msg:         "one retains, one release (from different subscriber)",
   176  			identifiers: []string{"i1"},
   177  			subscriberDefs: []SubscriberDefinition{
   178  				{ID: "s1"},
   179  				{ID: "s2"},
   180  			},
   181  			actions: []TransportAction{
   182  				RetainAction{InputIdentifierID: "i1", InputSubscriberID: "s1", ExpectedPeerID: "i1"},
   183  				ReleaseAction{
   184  					InputIdentifierID: "i1",
   185  					InputSubscriberID: "s2",
   186  					ExpectedErrType:   peer.ErrPeerHasNoReferenceToSubscriber{},
   187  				},
   188  			},
   189  			expectedPeers: []peerExpectation{
   190  				{id: "i1", subscribers: []string{"s1"}},
   191  			},
   192  		},
   193  		{
   194  			msg:         "multi peer retain/release",
   195  			identifiers: []string{"i1", "i2", "i3", "i4r", "i5r"},
   196  			subscriberDefs: []SubscriberDefinition{
   197  				{ID: "s1"},
   198  				{ID: "s2"},
   199  				{ID: "s3"},
   200  				{ID: "s4"},
   201  				{ID: "s5rnd"},
   202  				{ID: "s6rnd"},
   203  				{ID: "s7rnd"},
   204  			},
   205  			actions: []TransportAction{
   206  				// Retains/Releases of i1 (Retain/Release the random peers at the end)
   207  				RetainAction{InputIdentifierID: "i1", InputSubscriberID: "s5rnd", ExpectedPeerID: "i1"},
   208  				RetainAction{InputIdentifierID: "i1", InputSubscriberID: "s6rnd", ExpectedPeerID: "i1"},
   209  				RetainAction{InputIdentifierID: "i1", InputSubscriberID: "s1", ExpectedPeerID: "i1"},
   210  				RetainAction{InputIdentifierID: "i1", InputSubscriberID: "s2", ExpectedPeerID: "i1"},
   211  				ReleaseAction{InputIdentifierID: "i1", InputSubscriberID: "s5rnd"},
   212  				ReleaseAction{InputIdentifierID: "i1", InputSubscriberID: "s6rnd"},
   213  
   214  				// Retains/Releases of i2 (Retain then Release then Retain again)
   215  				RetainAction{InputIdentifierID: "i2", InputSubscriberID: "s2", ExpectedPeerID: "i2"},
   216  				RetainAction{InputIdentifierID: "i2", InputSubscriberID: "s3", ExpectedPeerID: "i2"},
   217  				ReleaseAction{InputIdentifierID: "i2", InputSubscriberID: "s2"},
   218  				ReleaseAction{InputIdentifierID: "i2", InputSubscriberID: "s3"},
   219  				RetainAction{InputIdentifierID: "i2", InputSubscriberID: "s2", ExpectedPeerID: "i2"},
   220  				RetainAction{InputIdentifierID: "i2", InputSubscriberID: "s3", ExpectedPeerID: "i2"},
   221  
   222  				// Retains/Releases of i3 (Retain/Release unrelated sub, then retain two)
   223  				RetainAction{InputIdentifierID: "i3", InputSubscriberID: "s7rnd", ExpectedPeerID: "i3"},
   224  				ReleaseAction{InputIdentifierID: "i3", InputSubscriberID: "s7rnd"},
   225  				RetainAction{InputIdentifierID: "i3", InputSubscriberID: "s3", ExpectedPeerID: "i3"},
   226  				RetainAction{InputIdentifierID: "i3", InputSubscriberID: "s4", ExpectedPeerID: "i3"},
   227  
   228  				// Retain/Release i4r on random subscriber
   229  				RetainAction{InputIdentifierID: "i4r", InputSubscriberID: "s5rnd", ExpectedPeerID: "i4r"},
   230  				ReleaseAction{InputIdentifierID: "i4r", InputSubscriberID: "s5rnd"},
   231  
   232  				// Retain/Release i5r on already used subscriber
   233  				RetainAction{InputIdentifierID: "i5r", InputSubscriberID: "s3", ExpectedPeerID: "i5r"},
   234  				ReleaseAction{InputIdentifierID: "i5r", InputSubscriberID: "s3"},
   235  			},
   236  			expectedPeers: []peerExpectation{
   237  				{id: "i1", subscribers: []string{"s1", "s2"}},
   238  				{id: "i2", subscribers: []string{"s2", "s3"}},
   239  				{id: "i3", subscribers: []string{"s3", "s4"}},
   240  			},
   241  		},
   242  	}
   243  
   244  	for _, tt := range tests {
   245  		t.Run(tt.msg, func(t *testing.T) {
   246  			mockCtrl := gomock.NewController(t)
   247  			defer mockCtrl.Finish()
   248  
   249  			transport := NewTransport()
   250  			defer transport.Stop()
   251  
   252  			deps := TransportDeps{
   253  				PeerIdentifiers: createPeerIdentifierMap(tt.identifiers),
   254  				Subscribers:     CreateSubscriberMap(mockCtrl, tt.subscriberDefs),
   255  			}
   256  			ApplyTransportActions(t, transport, tt.actions, deps)
   257  
   258  			assert.Len(t, transport.peers, len(tt.expectedPeers))
   259  			for _, expectedPeerNode := range tt.expectedPeers {
   260  				p, ok := transport.peers[expectedPeerNode.id]
   261  				assert.True(t, ok)
   262  
   263  				if assert.NotNil(t, p) {
   264  					assert.Equal(t, expectedPeerNode.id, p.Identifier())
   265  
   266  					// We can't look at the hostport subscribers directly so we'll
   267  					// attempt to remove subscribers and be sure that it doesn't error
   268  					assert.Len(t, expectedPeerNode.subscribers, p.NumSubscribers())
   269  					for _, sub := range expectedPeerNode.subscribers {
   270  						err := p.Unsubscribe(deps.Subscribers[sub])
   271  						assert.NoError(t, err, "peer %s did not have reference to subscriber %s", p.Identifier(), sub)
   272  					}
   273  				}
   274  			}
   275  		})
   276  	}
   277  }
   278  
   279  func TestTransportClient(t *testing.T) {
   280  	transport := NewTransport()
   281  
   282  	assert.NotNil(t, transport.client)
   283  }
   284  
   285  func TestTransportClientOpaqueOptions(t *testing.T) {
   286  	// Unfortunately the KeepAlive is obfuscated in the client, so we can't really
   287  	// assert this worked.
   288  	transport := NewTransport(
   289  		KeepAlive(testtime.Second),
   290  		MaxIdleConns(100),
   291  		MaxIdleConnsPerHost(10),
   292  		IdleConnTimeout(1*time.Second),
   293  		DisableCompression(),
   294  		DisableKeepAlives(),
   295  		ResponseHeaderTimeout(1*time.Second),
   296  	)
   297  
   298  	assert.NotNil(t, transport.client)
   299  }
   300  
   301  func TestDialContext(t *testing.T) {
   302  	errMsg := "my custom dialer error message"
   303  	dialContext := func(ctx context.Context, network, addr string) (net.Conn, error) {
   304  		return nil, errors.New(errMsg)
   305  	}
   306  
   307  	transport := NewTransport(DialContext(dialContext))
   308  
   309  	require.NoError(t, transport.Start())
   310  	defer func() { assert.NoError(t, transport.Stop()) }()
   311  
   312  	req, err := http.NewRequest("GET", "http://foo.bar", nil)
   313  	require.NoError(t, err)
   314  
   315  	outbound := transport.NewOutbound(ypeer.NewSingle(hostport.Identify("foo"), transport))
   316  	require.NoError(t, outbound.Start())
   317  	defer func() { assert.NoError(t, outbound.Stop()) }()
   318  
   319  	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
   320  	defer cancel()
   321  
   322  	_, err = outbound.RoundTrip(req.WithContext(ctx))
   323  	require.Error(t, err)
   324  	assert.Contains(t, err.Error(), errMsg)
   325  }
   326  
   327  type testIdentifier struct {
   328  	id string
   329  }
   330  
   331  func (i testIdentifier) Identifier() string {
   332  	return i.id
   333  }