go.uber.org/yarpc@v1.72.1/yarpcconfig/chooser_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 yarpcconfig_test
    22  
    23  import (
    24  	"context"
    25  	"errors"
    26  	"strings"
    27  	"testing"
    28  
    29  	"github.com/golang/mock/gomock"
    30  	"github.com/opentracing/opentracing-go"
    31  	"github.com/stretchr/testify/assert"
    32  	"github.com/stretchr/testify/require"
    33  	"go.uber.org/yarpc"
    34  	peerapi "go.uber.org/yarpc/api/peer"
    35  	"go.uber.org/yarpc/api/peer/peertest"
    36  	"go.uber.org/yarpc/api/transport"
    37  	"go.uber.org/yarpc/api/transport/transporttest"
    38  	"go.uber.org/yarpc/internal/interpolate"
    39  	"go.uber.org/yarpc/internal/testtime"
    40  	"go.uber.org/yarpc/internal/whitespace"
    41  	"go.uber.org/yarpc/peer"
    42  	"go.uber.org/yarpc/peer/hostport"
    43  	"go.uber.org/yarpc/peer/pendingheap"
    44  	"go.uber.org/yarpc/peer/roundrobin"
    45  	"go.uber.org/yarpc/peer/x/peerheap"
    46  	"go.uber.org/yarpc/transport/http"
    47  	"go.uber.org/yarpc/transport/tchannel"
    48  	"go.uber.org/yarpc/yarpcconfig"
    49  	"go.uber.org/yarpc/yarpctest"
    50  )
    51  
    52  func TestChooserConfigurator(t *testing.T) {
    53  	tests := []struct {
    54  		desc                 string
    55  		given                string
    56  		env                  map[string]string
    57  		overrideConfigurator func() *yarpcconfig.Configurator
    58  		wantErr              []string
    59  		test                 func(*testing.T, yarpc.Config)
    60  	}{
    61  		{
    62  			desc: "single static peer",
    63  			given: whitespace.Expand(`
    64  				transports:
    65  					fake-transport:
    66  						nop: ":1234"
    67  				outbounds:
    68  					their-service:
    69  						unary:
    70  							fake-transport:
    71  								nop: "*.*"
    72  								peer: 127.0.0.1:8080
    73  			`),
    74  			test: func(t *testing.T, c yarpc.Config) {
    75  				outbound, ok := c.Outbounds["their-service"]
    76  				require.True(t, ok, "config has outbound")
    77  
    78  				require.NotNil(t, outbound.Unary, "must have unary outbound")
    79  				unary, ok := outbound.Unary.(*yarpctest.FakeOutbound)
    80  				require.True(t, ok, "unary outbound must be fake outbound")
    81  				assert.Equal(t, "*.*", unary.NopOption(), "must have configured pattern")
    82  
    83  				transports := unary.Transports()
    84  				require.Equal(t, 1, len(transports), "must have one transport")
    85  
    86  				transport, ok := transports[0].(*yarpctest.FakeTransport)
    87  				require.True(t, ok, "must be a fake transport")
    88  				assert.Equal(t, ":1234", transport.NopOption(), "transport configured")
    89  
    90  				require.NotNil(t, unary.Chooser(), "must have chooser")
    91  				chooser, ok := unary.Chooser().(*peer.Single)
    92  				require.True(t, ok, "unary chooser must be a single peer chooser")
    93  
    94  				dispatcher := yarpc.NewDispatcher(c)
    95  				assert.NoError(t, dispatcher.Start(), "error starting")
    96  				assert.NoError(t, dispatcher.Stop(), "error stopping")
    97  
    98  				_ = chooser
    99  			},
   100  		},
   101  		{
   102  			desc: "custom chooser",
   103  			given: whitespace.Expand(`
   104  				transports:
   105  					fake-transport:
   106  						nop: ":1234"
   107  				outbounds:
   108  					their-service:
   109  						unary:
   110  							fake-transport:
   111  								fake-chooser:
   112  									nop: "*.*"
   113  			`),
   114  			test: func(t *testing.T, c yarpc.Config) {
   115  				outbound, ok := c.Outbounds["their-service"]
   116  				require.True(t, ok, "config has outbound")
   117  
   118  				require.NotNil(t, outbound.Unary, "must have unary outbound")
   119  				unary, ok := outbound.Unary.(*yarpctest.FakeOutbound)
   120  				require.True(t, ok, "unary outbound must be fake outbound")
   121  
   122  				transports := unary.Transports()
   123  				require.Equal(t, len(transports), 1, "must have one transport")
   124  
   125  				transport, ok := transports[0].(*yarpctest.FakeTransport)
   126  				require.True(t, ok, "must be a fake transport")
   127  				assert.Equal(t, transport.NopOption(), ":1234", "transport configured")
   128  
   129  				require.NotNil(t, unary.Chooser(), "must have chooser")
   130  				chooser, ok := unary.Chooser().(*yarpctest.FakePeerChooser)
   131  				require.True(t, ok, "unary chooser must be a fake peer chooser")
   132  				require.Equal(t, "*.*", chooser.Nop())
   133  
   134  				dispatcher := yarpc.NewDispatcher(c)
   135  				assert.NoError(t, dispatcher.Start(), "error starting")
   136  				assert.NoError(t, dispatcher.Stop(), "error stopping")
   137  
   138  				_ = chooser
   139  			},
   140  		},
   141  		{
   142  			desc: "multiple static peers",
   143  			given: whitespace.Expand(`
   144  				transports:
   145  					fake-transport:
   146  						nop: ":1234"
   147  				outbounds:
   148  					their-service:
   149  						unary:
   150  							fake-transport:
   151  								nop: "*.*"
   152  								fake-list:
   153  									peers:
   154  										- 127.0.0.1:8080
   155  										- 127.0.0.1:8081
   156  			`),
   157  			test: func(t *testing.T, c yarpc.Config) {
   158  				outbound, ok := c.Outbounds["their-service"]
   159  				require.True(t, ok, "config has outbound")
   160  
   161  				require.NotNil(t, outbound.Unary, "must have unary outbound")
   162  				unary, ok := outbound.Unary.(*yarpctest.FakeOutbound)
   163  				require.True(t, ok, "unary outbound must be fake outbound")
   164  
   165  				transports := unary.Transports()
   166  				require.Equal(t, len(transports), 1, "must have one transport")
   167  
   168  				transport, ok := transports[0].(*yarpctest.FakeTransport)
   169  				require.True(t, ok, "must be a fake transport")
   170  				assert.Equal(t, transport.NopOption(), ":1234", "transport configured")
   171  
   172  				require.NotNil(t, unary.Chooser(), "must have chooser")
   173  				chooser, ok := unary.Chooser().(*peer.BoundChooser)
   174  				require.True(t, ok, "unary chooser must be a bound chooser")
   175  
   176  				updater, ok := chooser.Updater().(*peer.PeersUpdater)
   177  				require.True(t, ok, "updater is a static peer list updater")
   178  
   179  				list, ok := chooser.ChooserList().(*yarpctest.FakePeerList)
   180  				require.True(t, ok, "list is a fake peer list")
   181  
   182  				dispatcher := yarpc.NewDispatcher(c)
   183  				assert.NoError(t, dispatcher.Start(), "error starting")
   184  				assert.NoError(t, dispatcher.Stop(), "error stopping")
   185  
   186  				_ = updater
   187  				_ = list
   188  			},
   189  		},
   190  		{
   191  			desc: "peer chooser preset",
   192  			given: whitespace.Expand(`
   193  				transports:
   194  					fake-transport:
   195  						nop: ":1234"
   196  				outbounds:
   197  					their-service:
   198  						unary:
   199  							fake-transport:
   200  								nop: "*.*"
   201  								with: fake-preset
   202  			`),
   203  			test: func(t *testing.T, c yarpc.Config) {
   204  				outbound, ok := c.Outbounds["their-service"]
   205  				require.True(t, ok, "config has outbound")
   206  
   207  				require.NotNil(t, outbound.Unary, "must have unary outbound")
   208  				unary, ok := outbound.Unary.(*yarpctest.FakeOutbound)
   209  				require.True(t, ok, "unary outbound must be fake outbound")
   210  
   211  				transports := unary.Transports()
   212  				require.Equal(t, len(transports), 1, "must have one transport")
   213  
   214  				transport, ok := transports[0].(*yarpctest.FakeTransport)
   215  				require.True(t, ok, "must be a fake transport")
   216  				assert.Equal(t, transport.NopOption(), ":1234", "transport configured")
   217  
   218  				require.NotNil(t, unary.Chooser(), "must have chooser")
   219  				chooser, ok := unary.Chooser().(*peer.BoundChooser)
   220  				require.True(t, ok, "unary chooser must be a bound chooser")
   221  
   222  				updater, ok := chooser.Updater().(*yarpctest.FakePeerListUpdater)
   223  				require.True(t, ok, "updater is a fake peer list updater")
   224  
   225  				list, ok := chooser.ChooserList().(*yarpctest.FakePeerList)
   226  				require.True(t, ok, "list is a fake peer list")
   227  
   228  				dispatcher := yarpc.NewDispatcher(c)
   229  				assert.NoError(t, dispatcher.Start(), "error starting")
   230  				assert.NoError(t, dispatcher.Stop(), "error stopping")
   231  
   232  				_ = updater
   233  				_ = list
   234  			},
   235  		},
   236  		{
   237  			desc: "using a peer list updater plugin",
   238  			given: whitespace.Expand(`
   239  				transports:
   240  					fake-transport:
   241  						nop: ":1234"
   242  				outbounds:
   243  					their-service:
   244  						unary:
   245  							fake-transport:
   246  								nop: "*.*"
   247  								fake-list:
   248  									fake-updater:
   249  										watch: true
   250  			`),
   251  			test: func(t *testing.T, c yarpc.Config) {
   252  				outbound, ok := c.Outbounds["their-service"]
   253  				require.True(t, ok, "config has outbound")
   254  
   255  				require.NotNil(t, outbound.Unary, "must have unary outbound")
   256  				unary, ok := outbound.Unary.(*yarpctest.FakeOutbound)
   257  				require.True(t, ok, "unary outbound must be fake outbound")
   258  
   259  				transports := unary.Transports()
   260  				require.Equal(t, len(transports), 1, "must have one transport")
   261  
   262  				transport, ok := transports[0].(*yarpctest.FakeTransport)
   263  				require.True(t, ok, "must be a fake transport")
   264  				assert.Equal(t, transport.NopOption(), ":1234", "transport configured")
   265  
   266  				require.NotNil(t, unary.Chooser(), "must have chooser")
   267  				chooser, ok := unary.Chooser().(*peer.BoundChooser)
   268  				require.True(t, ok, "unary chooser must be a bound chooser")
   269  
   270  				updater, ok := chooser.Updater().(*yarpctest.FakePeerListUpdater)
   271  				require.True(t, ok, "updater is a peer list updater")
   272  				assert.True(t, updater.Watch(), "peer list updater configured to watch")
   273  
   274  				list, ok := chooser.ChooserList().(*yarpctest.FakePeerList)
   275  				require.True(t, ok, "list is a fake peer list")
   276  				_ = list
   277  
   278  				dispatcher := yarpc.NewDispatcher(c)
   279  				assert.NoError(t, dispatcher.Start(), "error starting")
   280  				assert.NoError(t, dispatcher.Stop(), "error stopping")
   281  			},
   282  		},
   283  		{
   284  			desc: "use static peers with round robin and exercise choose",
   285  			given: whitespace.Expand(`
   286  				outbounds:
   287  					their-service:
   288  						unary:
   289  							fake-transport:
   290  								round-robin:
   291  									peers:
   292  									- 127.0.0.1:8080
   293  									- 127.0.0.1:8081
   294  			`),
   295  			test: func(t *testing.T, c yarpc.Config) {
   296  				outbound := c.Outbounds["their-service"]
   297  				unary := outbound.Unary.(*yarpctest.FakeOutbound)
   298  				transport := unary.Transports()[0].(*yarpctest.FakeTransport)
   299  				chooser := unary.Chooser().(*peer.BoundChooser)
   300  				binder := chooser.Updater()
   301  				list, ok := chooser.ChooserList().(*roundrobin.List)
   302  				require.True(t, ok, "chooser least pending")
   303  				_ = list
   304  
   305  				// Attempt to choose a peer
   306  				dispatcher := yarpc.NewDispatcher(c)
   307  				require.NoError(t, dispatcher.Start(), "error starting dispatcher")
   308  				defer func() {
   309  					require.NoError(t, dispatcher.Stop(), "error stopping dispatcher")
   310  				}()
   311  
   312  				require.True(t, transport.IsRunning(), "transport is running")
   313  				require.True(t, unary.IsRunning(), "outbound is running")
   314  				require.True(t, list.IsRunning(), "chooser is running")
   315  				require.True(t, binder.IsRunning(), "binder is running")
   316  
   317  				ctx := context.Background()
   318  				ctx, cancel := context.WithTimeout(ctx, testtime.Second)
   319  				defer cancel()
   320  				peer, onFinish, err := chooser.Choose(ctx, nil)
   321  				require.NoError(t, err, "error choosing peer")
   322  				defer onFinish(nil)
   323  
   324  				expectedPeers := []string{"127.0.0.1:8080", "127.0.0.1:8081"}
   325  				assert.Contains(t, expectedPeers, peer.Identifier(), "chooses one of the provided peers")
   326  			},
   327  		},
   328  		{
   329  			desc: "use round-robin chooser",
   330  			given: whitespace.Expand(`
   331  				outbounds:
   332  					their-service:
   333  						unary:
   334  							fake-transport:
   335  								round-robin:
   336  									fake-updater: {}
   337  			`),
   338  			test: func(t *testing.T, c yarpc.Config) {
   339  				outbound := c.Outbounds["their-service"]
   340  				unary := outbound.Unary.(*yarpctest.FakeOutbound)
   341  				chooser := unary.Chooser().(*peer.BoundChooser)
   342  				_, ok := chooser.ChooserList().(*roundrobin.List)
   343  				require.True(t, ok, "use round robin")
   344  			},
   345  		},
   346  		{
   347  			desc: "use round-robin chooser with capacity",
   348  			given: whitespace.Expand(`
   349  				outbounds:
   350  					their-service:
   351  						unary:
   352  							fake-transport:
   353  								round-robin:
   354  									capacity: 50
   355  									fake-updater: {}
   356  			`),
   357  			test: func(t *testing.T, c yarpc.Config) {
   358  				outbound := c.Outbounds["their-service"]
   359  				unary := outbound.Unary.(*yarpctest.FakeOutbound)
   360  				chooser := unary.Chooser().(*peer.BoundChooser)
   361  				_, ok := chooser.ChooserList().(*roundrobin.List)
   362  				require.True(t, ok, "use round robin")
   363  			},
   364  		},
   365  		{
   366  			desc: "use least-pending chooser",
   367  			given: whitespace.Expand(`
   368  				outbounds:
   369  					their-service:
   370  						unary:
   371  							fake-transport:
   372  								least-pending:
   373  									fake-updater: {}
   374  			`),
   375  			test: func(t *testing.T, c yarpc.Config) {
   376  				outbound := c.Outbounds["their-service"]
   377  				unary := outbound.Unary.(*yarpctest.FakeOutbound)
   378  				chooser := unary.Chooser().(*peer.BoundChooser)
   379  				list, ok := chooser.ChooserList().(*peerheap.List)
   380  				require.True(t, ok, "use peer heap")
   381  				_ = list
   382  			},
   383  		},
   384  		{
   385  			desc: "use fewest-pending-requests chooser",
   386  			given: whitespace.Expand(`
   387  				outbounds:
   388  					their-service:
   389  						unary:
   390  							fake-transport:
   391  								fewest-pending-requests:
   392  									fake-updater: {}
   393  			`),
   394  			test: func(t *testing.T, c yarpc.Config) {
   395  				outbound := c.Outbounds["their-service"]
   396  				unary := outbound.Unary.(*yarpctest.FakeOutbound)
   397  				chooser := unary.Chooser().(*peer.BoundChooser)
   398  				_, ok := chooser.ChooserList().(*pendingheap.List)
   399  				require.True(t, ok, "use pending heap")
   400  			},
   401  		},
   402  		{
   403  			desc: "use fewest-pending-requests chooser with capacity",
   404  			given: whitespace.Expand(`
   405  				outbounds:
   406  					their-service:
   407  						unary:
   408  							fake-transport:
   409  								fewest-pending-requests:
   410  									capacity: 50
   411  									fake-updater: {}
   412  			`),
   413  			test: func(t *testing.T, c yarpc.Config) {
   414  				outbound := c.Outbounds["their-service"]
   415  				unary := outbound.Unary.(*yarpctest.FakeOutbound)
   416  				chooser := unary.Chooser().(*peer.BoundChooser)
   417  				_, ok := chooser.ChooserList().(*pendingheap.List)
   418  				require.True(t, ok, "use pending heap")
   419  			},
   420  		},
   421  		{
   422  			desc: "HTTP single peer implied by URL",
   423  			given: whitespace.Expand(`
   424  				outbounds:
   425  					their-service:
   426  						unary:
   427  							http:
   428  								url: "https://127.0.0.1/rpc"
   429  			`),
   430  			test: func(t *testing.T, c yarpc.Config) {
   431  				outbound, ok := c.Outbounds["their-service"]
   432  				require.True(t, ok, "config has outbound")
   433  
   434  				require.NotNil(t, outbound.Unary, "must have unary outbound")
   435  				unary, ok := outbound.Unary.(*http.Outbound)
   436  				require.True(t, ok, "unary outbound must be HTTP outbound")
   437  
   438  				transports := unary.Transports()
   439  				require.Equal(t, 1, len(transports), "must have one transport")
   440  
   441  				transport, ok := transports[0].(*http.Transport)
   442  				require.True(t, ok, "must be an HTTP transport")
   443  
   444  				require.NotNil(t, unary.Chooser(), "must have chooser")
   445  				chooser, ok := unary.Chooser().(*peer.Single)
   446  				require.True(t, ok, "unary chooser must be a single peer chooser")
   447  
   448  				dispatcher := yarpc.NewDispatcher(c)
   449  				assert.NoError(t, dispatcher.Start(), "error starting")
   450  				assert.NoError(t, dispatcher.Stop(), "error stopping")
   451  
   452  				_ = transport
   453  				_ = chooser
   454  			},
   455  		},
   456  		{
   457  			desc: "HTTP",
   458  			given: whitespace.Expand(`
   459  				outbounds:
   460  					their-service:
   461  						unary:
   462  							http:
   463  								url: "https://service.example.com/rpc"
   464  								peer: "127.0.0.1"
   465  			`),
   466  			test: func(t *testing.T, c yarpc.Config) {
   467  				outbound, ok := c.Outbounds["their-service"]
   468  				require.True(t, ok, "config has outbound")
   469  
   470  				require.NotNil(t, outbound.Unary, "must have unary outbound")
   471  				unary, ok := outbound.Unary.(*http.Outbound)
   472  				require.True(t, ok, "unary outbound must be HTTP outbound")
   473  
   474  				transports := unary.Transports()
   475  				require.Equal(t, 1, len(transports), "must have one transport")
   476  
   477  				transport, ok := transports[0].(*http.Transport)
   478  				require.True(t, ok, "must be an HTTP transport")
   479  
   480  				require.NotNil(t, unary.Chooser(), "must have chooser")
   481  				chooser, ok := unary.Chooser().(*peer.Single)
   482  				require.True(t, ok, "unary chooser must be a single peer chooser")
   483  
   484  				dispatcher := yarpc.NewDispatcher(c)
   485  				assert.NoError(t, dispatcher.Start(), "error starting")
   486  				assert.NoError(t, dispatcher.Stop(), "error stopping")
   487  
   488  				_ = transport
   489  				_ = chooser
   490  			},
   491  		},
   492  		{
   493  			desc: "tchannel transport",
   494  			given: whitespace.Expand(`
   495  				outbounds:
   496  					their-service:
   497  						unary:
   498  							tchannel:
   499  								peer: 127.0.0.1:4040
   500  			`),
   501  			test: func(t *testing.T, c yarpc.Config) {
   502  				outbound, ok := c.Outbounds["their-service"]
   503  				require.True(t, ok, "config has outbound")
   504  
   505  				require.NotNil(t, outbound.Unary, "must have unary outbound")
   506  				unary, ok := outbound.Unary.(*tchannel.Outbound)
   507  				require.True(t, ok, "unary outbound must be TChannel outbound")
   508  
   509  				transports := unary.Transports()
   510  				require.Equal(t, 1, len(transports), "must have one transport")
   511  
   512  				transport, ok := transports[0].(*tchannel.Transport)
   513  				require.True(t, ok, "must be an TChannel transport")
   514  
   515  				require.NotNil(t, unary.Chooser(), "must have chooser")
   516  				chooser, ok := unary.Chooser().(*peer.Single)
   517  				require.True(t, ok, "unary chooser must be a single peer chooser")
   518  
   519  				dispatcher := yarpc.NewDispatcher(c)
   520  				assert.NoError(t, dispatcher.Start(), "error starting")
   521  				assert.NoError(t, dispatcher.Stop(), "error stopping")
   522  
   523  				_ = transport
   524  				_ = chooser
   525  			},
   526  		},
   527  		{
   528  			desc: "invalid peer chooser",
   529  			given: whitespace.Expand(`
   530  				outbounds:
   531  					their-service:
   532  						unary:
   533  							fake-transport:
   534  								bogus-list: {}
   535  			`),
   536  			wantErr: []string{
   537  				`failed to configure unary outbound for "their-service": `,
   538  				`no recognized peer list or chooser "bogus-list"`,
   539  				`need one of`,
   540  				`fake-list`,
   541  				`least-pending`,
   542  				`round-robin`,
   543  			},
   544  		},
   545  		{
   546  			desc: "invalid peer list",
   547  			given: whitespace.Expand(`
   548  				outbounds:
   549  					their-service:
   550  						unary:
   551  							fake-transport:
   552  								bogus-list:
   553  									fake-updater: {}
   554  			`),
   555  			wantErr: []string{
   556  				`failed to configure unary outbound for "their-service": `,
   557  				`no recognized peer list or chooser "bogus-list"`,
   558  				`need one of`,
   559  				`fake-list`,
   560  				`least-pending`,
   561  				`round-robin`,
   562  			},
   563  		},
   564  		{
   565  			desc: "invalid peer chooser preset",
   566  			given: whitespace.Expand(`
   567  				outbounds:
   568  					their-service:
   569  						unary:
   570  							fake-transport:
   571  								with: bogus
   572  			`),
   573  			wantErr: []string{
   574  				`failed to configure unary outbound for "their-service": `,
   575  				`no recognized peer chooser preset "bogus"`,
   576  				`need one of`,
   577  				`fake`,
   578  			},
   579  		},
   580  		{
   581  			desc: "invalid peer chooser decode error",
   582  			given: whitespace.Expand(`
   583  				transports:
   584  					fake-transport:
   585  						nop: ":1234"
   586  				outbounds:
   587  					their-service:
   588  						unary:
   589  							fake-transport:
   590  								nop: "*.*"
   591  								fake-chooser:
   592  									invalidValue: test
   593  			`),
   594  			wantErr: []string{
   595  				`failed to configure unary outbound for "their-service": `,
   596  				`failed to decode`,
   597  			},
   598  		},
   599  		{
   600  			desc: "invalid peer chooser decode",
   601  			given: whitespace.Expand(`
   602  				transports:
   603  					fake-transport:
   604  						nop: ":1234"
   605  				outbounds:
   606  					their-service:
   607  						unary:
   608  							fake-transport:
   609  								nop: "*.*"
   610  								fake-chooser:
   611  									- 127.0.0.1:8080
   612  									- 127.0.0.1:8081
   613  			`),
   614  			wantErr: []string{
   615  				`failed to configure unary outbound for "their-service": `,
   616  				`failed to read attribute "fake-chooser"`,
   617  			},
   618  		},
   619  		{
   620  			desc: "invalid peer list decode",
   621  			given: whitespace.Expand(`
   622  				transports:
   623  					fake-transport:
   624  						nop: ":1234"
   625  				outbounds:
   626  					their-service:
   627  						unary:
   628  							fake-transport:
   629  								nop: "*.*"
   630  								fake-list:
   631  									- 127.0.0.1:8080
   632  									- 127.0.0.1:8081
   633  			`),
   634  			wantErr: []string{
   635  				`failed to configure unary outbound for "their-service": `,
   636  				`failed to read attribute "fake-list"`,
   637  			},
   638  		},
   639  		{
   640  			desc: "invalid peer list updater",
   641  			given: whitespace.Expand(`
   642  				outbounds:
   643  					their-service:
   644  						unary:
   645  							fake-transport:
   646  								fake-list:
   647  									bogus-updater: 10
   648  			`),
   649  			wantErr: []string{
   650  				`failed to configure unary outbound for "their-service": `,
   651  				`no recognized peer list updater in config`,
   652  				`got bogus-updater`,
   653  				`need one of fake-updater, invalid-updater`,
   654  			},
   655  		},
   656  		{
   657  			desc: "too many peer list updaters",
   658  			given: whitespace.Expand(`
   659  				outbounds:
   660  					their-service:
   661  						unary:
   662  							fake-transport:
   663  								nop: "*.*"
   664  								fake-list:
   665  									fake-updater:
   666  										- 127.0.0.1:8080
   667  										- 127.0.0.1:8081
   668  									invalid-updater:
   669  										- 127.0.0.1:8080
   670  										- 127.0.0.1:8081
   671  			`),
   672  			wantErr: []string{
   673  				`failed to configure unary outbound for "their-service": `,
   674  				`found too many peer list updaters in config: got`,
   675  				"fake-updater", "invalid-updater",
   676  			},
   677  		},
   678  		{
   679  			desc: "invalid peer list updater decode",
   680  			given: whitespace.Expand(`
   681  				transports:
   682  					fake-transport:
   683  						nop: ":1234"
   684  				outbounds:
   685  					their-service:
   686  						unary:
   687  							fake-transport:
   688  								nop: "*.*"
   689  								fake-list:
   690  									fake-updater:
   691  										- 127.0.0.1:8080
   692  										- 127.0.0.1:8081
   693  			`),
   694  			wantErr: []string{
   695  				`failed to configure unary outbound for "their-service": `,
   696  				`failed to read attribute "fake-updater"`,
   697  			},
   698  		},
   699  		{
   700  			desc: "extraneous config in combination with single peer",
   701  			given: whitespace.Expand(`
   702  				outbounds:
   703  					their-service:
   704  						unary:
   705  							fake-transport:
   706  								peer: a
   707  								conspicuously: present
   708  			`),
   709  			wantErr: []string{
   710  				`failed to configure unary outbound for "their-service": `,
   711  				`unrecognized attributes in outbound config: `,
   712  				`conspicuously`,
   713  				`present`,
   714  			},
   715  		},
   716  		{
   717  			desc: "extraneous transport config in combination with list config",
   718  			given: whitespace.Expand(`
   719  				outbounds:
   720  					their-service:
   721  						unary:
   722  							fake-transport:
   723  								conspicuously: present
   724  								fake-list:
   725  									peers:
   726  										- 127.0.0.1:8080
   727  										- 127.0.0.1:8081
   728  			`),
   729  			wantErr: []string{
   730  				`failed to configure unary outbound for "their-service": `,
   731  				`unrecognized attributes in outbound config: `,
   732  				`conspicuously`,
   733  				`present`,
   734  			},
   735  		},
   736  		{
   737  			desc: "extraneous config in combination with multiple peers",
   738  			given: whitespace.Expand(`
   739  				outbounds:
   740  					their-service:
   741  						unary:
   742  							fake-transport:
   743  								fake-list:
   744  									peers:
   745  										- 127.0.0.1:8080
   746  										- 127.0.0.1:8081
   747  									conspicuously: present
   748  			`),
   749  			wantErr: []string{
   750  				`failed to configure unary outbound for "their-service": `,
   751  				`has invalid keys:`,
   752  				`conspicuously`,
   753  			},
   754  		},
   755  		{
   756  			desc: "extraneous config in combination with preset",
   757  			given: whitespace.Expand(`
   758  				outbounds:
   759  					their-service:
   760  						unary:
   761  							fake-transport:
   762  								with: fake-preset
   763  								conspicuously: present
   764  			`),
   765  			wantErr: []string{
   766  				`failed to configure unary outbound for "their-service": `,
   767  				`conspicuously`,
   768  				`present`,
   769  			},
   770  		},
   771  		{
   772  			desc: "invalid list peers",
   773  			given: whitespace.Expand(`
   774  				outbounds:
   775  					their-service:
   776  						unary:
   777  							fake-transport:
   778  								fake-list:
   779  									peers:
   780  										host1: 127.0.0.1:8080
   781  										host2: 127.0.0.1:8081
   782  			`),
   783  			wantErr: []string{
   784  				`failed to configure unary outbound for "their-service": `,
   785  				`failed to read attribute "peers"`,
   786  			},
   787  		},
   788  		{
   789  			desc: "extraneous config in combination with custom updater",
   790  			given: whitespace.Expand(`
   791  				outbounds:
   792  					their-service:
   793  						unary:
   794  							fake-transport:
   795  								fake-list:
   796  									fake-updater:
   797  										watch: true
   798  										conspicuously: present
   799  			`),
   800  			wantErr: []string{
   801  				`failed to configure unary outbound for "their-service": `,
   802  				`conspicuously`,
   803  			},
   804  		},
   805  		{
   806  			desc: "missing peer list config",
   807  			given: whitespace.Expand(`
   808  				outbounds:
   809  					their-service:
   810  						unary:
   811  							fake-transport: {}
   812  			`),
   813  			wantErr: []string{
   814  				`failed to configure unary outbound for "their-service": `,
   815  				`no peer list or chooser provided in config`,
   816  				`need one of`,
   817  				`fake-list`,
   818  				`least-pending`,
   819  				`round-robin`,
   820  			},
   821  		},
   822  		{
   823  			desc: "invalid peer chooser builder",
   824  			given: whitespace.Expand(`
   825  				outbounds:
   826  					their-service:
   827  						unary:
   828  							fake-transport:
   829  								invalid-chooser: {}
   830  			`),
   831  			wantErr: []string{
   832  				`failed to configure unary outbound for "their-service": `,
   833  				`could not create invalid-chooser`,
   834  			},
   835  		},
   836  		{
   837  			desc: "invalid peer list builder",
   838  			given: whitespace.Expand(`
   839  				outbounds:
   840  					their-service:
   841  						unary:
   842  							fake-transport:
   843  								invalid-list:
   844  									fake-updater: {}
   845  			`),
   846  			wantErr: []string{
   847  				`failed to configure unary outbound for "their-service": `,
   848  				`could not create invalid-list`,
   849  			},
   850  		},
   851  		{
   852  			desc: "invalid peer list updater builder",
   853  			given: whitespace.Expand(`
   854  				outbounds:
   855  					their-service:
   856  						unary:
   857  							fake-transport:
   858  								fake-list:
   859  									invalid-updater: {}
   860  			`),
   861  			wantErr: []string{
   862  				`failed to configure unary outbound for "their-service": `,
   863  				`could not create invalid-updater`,
   864  			},
   865  		},
   866  		{
   867  			desc: "invalid oneway peer list config",
   868  			given: whitespace.Expand(`
   869  				outbounds:
   870  					their-service:
   871  						oneway:
   872  							fake-transport:
   873  								fake-list:
   874  									invalid-updater: {}
   875  			`),
   876  			wantErr: []string{
   877  				`failed to configure oneway outbound for "their-service": `,
   878  				`could not create invalid-updater`,
   879  			},
   880  		},
   881  		{
   882  			desc: "invalid stream peer list config",
   883  			given: whitespace.Expand(`
   884  				outbounds:
   885  					their-service:
   886  						stream:
   887  							fake-transport:
   888  								fake-list:
   889  									invalid-updater: {}
   890  			`),
   891  			wantErr: []string{
   892  				`failed to configure stream outbound for "their-service": `,
   893  				`could not create invalid-updater`,
   894  			},
   895  		},
   896  		{
   897  			desc: "interpolation fallback",
   898  			given: whitespace.Expand(`
   899  				transports:
   900  					fake-transport:
   901  						nop: ":${FIRST_VAR:1234}"
   902  				outbounds:
   903  					their-service:
   904  						unary:
   905  							fake-transport:
   906  								nop: "${SECOND_VAR:*.*}"
   907  								peer: 127.0.0.1:${THIRD_VAR:8080}
   908  			`),
   909  			test: func(t *testing.T, c yarpc.Config) {
   910  				outbound, ok := c.Outbounds["their-service"]
   911  				require.True(t, ok, "config has outbound")
   912  
   913  				require.NotNil(t, outbound.Unary, "must have unary outbound")
   914  				unary, ok := outbound.Unary.(*yarpctest.FakeOutbound)
   915  				require.True(t, ok, "unary outbound must be fake outbound")
   916  				assert.Equal(t, "*.*", unary.NopOption(), "must have configured pattern")
   917  
   918  				transports := unary.Transports()
   919  				require.Equal(t, 1, len(transports), "must have one transport")
   920  
   921  				transport, ok := transports[0].(*yarpctest.FakeTransport)
   922  				require.True(t, ok, "must be a fake transport")
   923  				assert.Equal(t, ":1234", transport.NopOption(), "transport configured")
   924  
   925  				require.NotNil(t, unary.Chooser(), "must have chooser")
   926  				chooser, ok := unary.Chooser().(*peer.Single)
   927  				require.True(t, ok, "unary chooser must be a single peer chooser")
   928  				assert.Equal(t, "127.0.0.1:8080", chooser.Introspect().Peers[0].Identifier, "incorrect peer")
   929  
   930  				dispatcher := yarpc.NewDispatcher(c)
   931  				assert.NoError(t, dispatcher.Start(), "error starting")
   932  				assert.NoError(t, dispatcher.Stop(), "error stopping")
   933  
   934  				_ = chooser
   935  			},
   936  		},
   937  		{
   938  			desc: "interpolation to env var",
   939  			given: whitespace.Expand(`
   940  				transports:
   941  					fake-transport:
   942  						nop: ":${FIRST_VAR:1234}"
   943  				outbounds:
   944  					their-service:
   945  						unary:
   946  							fake-transport:
   947  								nop: "${SECOND_VAR:*.*}"
   948  								peer: 127.0.0.1:${THIRD_VAR:808-0}
   949  			`),
   950  			env: map[string]string{
   951  				"FIRST_VAR":  "3456",
   952  				"SECOND_VAR": "A*A",
   953  				"THIRD_VAR":  "9000",
   954  			},
   955  			test: func(t *testing.T, c yarpc.Config) {
   956  				outbound, ok := c.Outbounds["their-service"]
   957  				require.True(t, ok, "config has outbound")
   958  
   959  				require.NotNil(t, outbound.Unary, "must have unary outbound")
   960  				unary, ok := outbound.Unary.(*yarpctest.FakeOutbound)
   961  				require.True(t, ok, "unary outbound must be fake outbound")
   962  				assert.Equal(t, "A*A", unary.NopOption(), "must have configured pattern")
   963  
   964  				transports := unary.Transports()
   965  				require.Equal(t, 1, len(transports), "must have one transport")
   966  
   967  				transport, ok := transports[0].(*yarpctest.FakeTransport)
   968  				require.True(t, ok, "must be a fake transport")
   969  				assert.Equal(t, ":3456", transport.NopOption(), "transport configured")
   970  
   971  				require.NotNil(t, unary.Chooser(), "must have chooser")
   972  				chooser, ok := unary.Chooser().(*peer.Single)
   973  				require.True(t, ok, "unary chooser must be a single peer chooser")
   974  				assert.Equal(t, "127.0.0.1:9000", chooser.Introspect().Peers[0].Identifier, "incorrect peer")
   975  
   976  				dispatcher := yarpc.NewDispatcher(c)
   977  				assert.NoError(t, dispatcher.Start(), "error starting")
   978  				assert.NoError(t, dispatcher.Stop(), "error stopping")
   979  
   980  				_ = chooser
   981  			},
   982  		},
   983  		{
   984  			desc: "interpolation to env var for chooser and updater",
   985  			given: whitespace.Expand(`
   986  				outbounds:
   987  					their-service:
   988  						unary:
   989  							fake-transport:
   990  								fake-list:
   991  									nop: "${LIST_VAR:list}"
   992  									fake-updater:
   993  										nop: "${UPDATER_VAR:updater}"
   994  										watch: true
   995  			`),
   996  			env: map[string]string{
   997  				"LIST_VAR":    "envlist",
   998  				"UPDATER_VAR": "envupdater",
   999  			},
  1000  			test: func(t *testing.T, c yarpc.Config) {
  1001  				outbound, ok := c.Outbounds["their-service"]
  1002  				require.True(t, ok, "config has outbound")
  1003  
  1004  				require.NotNil(t, outbound.Unary, "must have unary outbound")
  1005  				unary, ok := outbound.Unary.(*yarpctest.FakeOutbound)
  1006  				require.True(t, ok, "unary outbound must be fake outbound")
  1007  
  1008  				transports := unary.Transports()
  1009  				require.Equal(t, 1, len(transports), "must have one transport")
  1010  
  1011  				require.NotNil(t, unary.Chooser(), "must have chooser")
  1012  				chooser, ok := unary.Chooser().(*peer.BoundChooser)
  1013  				require.True(t, ok, "unary chooser must be a bound chooser")
  1014  
  1015  				updater, ok := chooser.Updater().(*yarpctest.FakePeerListUpdater)
  1016  				require.True(t, ok, "updater is a peer list updater")
  1017  				assert.True(t, updater.Watch(), "peer list updater configured to watch")
  1018  				assert.Equal(t, "envupdater", updater.Nop(), "did not properly interpolate variables for peer updater")
  1019  
  1020  				list, ok := chooser.ChooserList().(*yarpctest.FakePeerList)
  1021  				require.True(t, ok, "list is a fake peer list")
  1022  				assert.Equal(t, "envlist", list.Nop(), "did not properly interpolate variables for peer list")
  1023  				_ = list
  1024  
  1025  				dispatcher := yarpc.NewDispatcher(c)
  1026  				assert.NoError(t, dispatcher.Start(), "error starting")
  1027  				assert.NoError(t, dispatcher.Stop(), "error stopping")
  1028  
  1029  				_ = chooser
  1030  			},
  1031  		},
  1032  		{
  1033  			desc: "invalid peer list updater",
  1034  			given: whitespace.Expand(`
  1035  				outbounds:
  1036  					their-service:
  1037  						unary:
  1038  							fake-transport:
  1039  								fake-list:
  1040  									bogus-updater: 10
  1041  			`),
  1042  			overrideConfigurator: func() *yarpcconfig.Configurator {
  1043  				// Unlike yarpctest.NewFakeConfigurator, this does _not_ have
  1044  				// any registered peer list updaters.
  1045  				configer := yarpcconfig.New(yarpcconfig.InterpolationResolver(mapVariableResolver(nil)))
  1046  				configer.MustRegisterTransport(yarpctest.FakeTransportSpec())
  1047  				configer.MustRegisterPeerChooser(yarpctest.FakePeerChooserSpec())
  1048  				configer.MustRegisterPeerList(yarpctest.FakePeerListSpec())
  1049  				configer.MustRegisterTransport(http.TransportSpec())
  1050  				configer.MustRegisterTransport(tchannel.TransportSpec(tchannel.Tracer(opentracing.NoopTracer{})))
  1051  				configer.MustRegisterPeerList(peerheap.Spec())
  1052  				configer.MustRegisterPeerList(pendingheap.Spec())
  1053  				configer.MustRegisterPeerList(roundrobin.Spec())
  1054  				configer.MustRegisterPeerChooser(invalidPeerChooserSpec())
  1055  				configer.MustRegisterPeerList(invalidPeerListSpec())
  1056  				//configer.MustRegisterPeerListUpdater(invalidPeerListUpdaterSpec()) //None
  1057  				return configer
  1058  			},
  1059  			wantErr: []string{
  1060  				`failed to configure unary outbound for "their-service": `,
  1061  				`no recognized peer list updater in config`,
  1062  				`got bogus-updater`,
  1063  				`no peer list updaters are registered`,
  1064  			},
  1065  		},
  1066  	}
  1067  
  1068  	for _, tt := range tests {
  1069  		t.Run(tt.desc, func(t *testing.T) {
  1070  			var configer *yarpcconfig.Configurator
  1071  			if tt.overrideConfigurator != nil {
  1072  				configer = tt.overrideConfigurator()
  1073  			} else {
  1074  				configer = yarpctest.NewFakeConfigurator(yarpcconfig.InterpolationResolver(mapVariableResolver(tt.env)))
  1075  				configer.MustRegisterTransport(http.TransportSpec())
  1076  				configer.MustRegisterTransport(tchannel.TransportSpec(tchannel.Tracer(opentracing.NoopTracer{})))
  1077  				configer.MustRegisterPeerList(peerheap.Spec())
  1078  				configer.MustRegisterPeerList(pendingheap.Spec())
  1079  				configer.MustRegisterPeerList(roundrobin.Spec())
  1080  				configer.MustRegisterPeerChooser(invalidPeerChooserSpec())
  1081  				configer.MustRegisterPeerList(invalidPeerListSpec())
  1082  				configer.MustRegisterPeerListUpdater(invalidPeerListUpdaterSpec())
  1083  			}
  1084  
  1085  			config, err := configer.LoadConfigFromYAML("fake-service", strings.NewReader(tt.given))
  1086  			if err != nil {
  1087  				if len(tt.wantErr) > 0 {
  1088  					// Check for every required error substring
  1089  					for _, wantErr := range tt.wantErr {
  1090  						require.Contains(t, err.Error(), wantErr, "expected error")
  1091  					}
  1092  				} else {
  1093  					require.NoError(t, err, "error loading config")
  1094  				}
  1095  			} else if len(tt.wantErr) > 0 {
  1096  				require.Error(t, err, "expected error")
  1097  			}
  1098  			if tt.test != nil {
  1099  				tt.test(t, config)
  1100  			}
  1101  		})
  1102  	}
  1103  }
  1104  
  1105  type invalidPeerChooserConfig struct {
  1106  }
  1107  
  1108  func buildInvalidPeerChooserConfig(c *invalidPeerChooserConfig, t peerapi.Transport, kit *yarpcconfig.Kit) (peerapi.Chooser, error) {
  1109  	return nil, errors.New("could not create invalid-chooser")
  1110  }
  1111  
  1112  func invalidPeerChooserSpec() yarpcconfig.PeerChooserSpec {
  1113  	return yarpcconfig.PeerChooserSpec{
  1114  		Name:             "invalid-chooser",
  1115  		BuildPeerChooser: buildInvalidPeerChooserConfig,
  1116  	}
  1117  }
  1118  
  1119  type invalidPeerListConfig struct {
  1120  }
  1121  
  1122  func buildInvalidPeerListConfig(c *invalidPeerListConfig, t peerapi.Transport, kit *yarpcconfig.Kit) (peerapi.ChooserList, error) {
  1123  	return nil, errors.New("could not create invalid-list")
  1124  }
  1125  
  1126  func invalidPeerListSpec() yarpcconfig.PeerListSpec {
  1127  	return yarpcconfig.PeerListSpec{
  1128  		Name:          "invalid-list",
  1129  		BuildPeerList: buildInvalidPeerListConfig,
  1130  	}
  1131  }
  1132  
  1133  type invalidPeerListUpdaterConfig struct {
  1134  }
  1135  
  1136  func buildInvalidPeerListUpdater(c *invalidPeerListUpdaterConfig, kit *yarpcconfig.Kit) (peerapi.Binder, error) {
  1137  	return nil, errors.New("could not create invalid-updater")
  1138  }
  1139  
  1140  func invalidPeerListUpdaterSpec() yarpcconfig.PeerListUpdaterSpec {
  1141  	return yarpcconfig.PeerListUpdaterSpec{
  1142  		Name:                 "invalid-updater",
  1143  		BuildPeerListUpdater: buildInvalidPeerListUpdater,
  1144  	}
  1145  }
  1146  
  1147  func TestBuildPeerListInvalidKit(t *testing.T) {
  1148  	mockCtrl := gomock.NewController(t)
  1149  	defer mockCtrl.Finish()
  1150  
  1151  	// We build a fake InboundConfig that embeds the PeerList. This will let
  1152  	// us call PeerList.BuildPeerList with the wrong Kit.
  1153  	type inboundConfig struct {
  1154  		yarpcconfig.PeerChooser
  1155  	}
  1156  
  1157  	configer := yarpctest.NewFakeConfigurator()
  1158  	configer.MustRegisterTransport(yarpcconfig.TransportSpec{
  1159  		Name: "foo",
  1160  		BuildTransport: func(struct{}, *yarpcconfig.Kit) (transport.Transport, error) {
  1161  			return transporttest.NewMockTransport(mockCtrl), nil
  1162  		},
  1163  		BuildInbound: func(cfg *inboundConfig, _ transport.Transport, k *yarpcconfig.Kit) (transport.Inbound, error) {
  1164  			_, err := cfg.BuildPeerChooser(peertest.NewMockTransport(mockCtrl), hostport.Identify, k)
  1165  			assert.Error(t, err, "BuildPeerList should fail with an invalid Kit")
  1166  			return transporttest.NewMockInbound(mockCtrl), err
  1167  		},
  1168  	})
  1169  
  1170  	_, err := configer.LoadConfig("myservice", map[string]interface{}{
  1171  		"inbounds": map[string]interface{}{
  1172  			"foo": map[string]interface{}{"with": "irrelevant"},
  1173  		},
  1174  	})
  1175  	require.Error(t, err, "LoadConfig should fail")
  1176  	assert.Contains(t, err.Error(),
  1177  		"invalid Kit: make sure you passed in the same Kit your Build function received")
  1178  }
  1179  
  1180  func mapVariableResolver(m map[string]string) interpolate.VariableResolver {
  1181  	return func(name string) (value string, ok bool) {
  1182  		value, ok = m[name]
  1183  		return
  1184  	}
  1185  }