github.com/ydb-platform/ydb-go-sdk/v3@v3.89.2/internal/balancer/connections_state_test.go (about)

     1  package balancer
     2  
     3  import (
     4  	"context"
     5  	"strings"
     6  	"testing"
     7  
     8  	"github.com/stretchr/testify/require"
     9  
    10  	balancerConfig "github.com/ydb-platform/ydb-go-sdk/v3/internal/balancer/config"
    11  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/conn"
    12  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/endpoint"
    13  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/mock"
    14  )
    15  
    16  func TestConnsToNodeIDMap(t *testing.T) {
    17  	table := []struct {
    18  		name   string
    19  		source []conn.Conn
    20  		res    map[uint32]conn.Conn
    21  	}{
    22  		{
    23  			name:   "Empty",
    24  			source: nil,
    25  			res:    nil,
    26  		},
    27  		{
    28  			name: "Zero",
    29  			source: []conn.Conn{
    30  				&mock.Conn{NodeIDField: 0},
    31  			},
    32  			res: map[uint32]conn.Conn{
    33  				0: &mock.Conn{NodeIDField: 0},
    34  			},
    35  		},
    36  		{
    37  			name: "NonZero",
    38  			source: []conn.Conn{
    39  				&mock.Conn{NodeIDField: 1},
    40  				&mock.Conn{NodeIDField: 10},
    41  			},
    42  			res: map[uint32]conn.Conn{
    43  				1:  &mock.Conn{NodeIDField: 1},
    44  				10: &mock.Conn{NodeIDField: 10},
    45  			},
    46  		},
    47  		{
    48  			name: "Combined",
    49  			source: []conn.Conn{
    50  				&mock.Conn{NodeIDField: 1},
    51  				&mock.Conn{NodeIDField: 0},
    52  				&mock.Conn{NodeIDField: 10},
    53  			},
    54  			res: map[uint32]conn.Conn{
    55  				0:  &mock.Conn{NodeIDField: 0},
    56  				1:  &mock.Conn{NodeIDField: 1},
    57  				10: &mock.Conn{NodeIDField: 10},
    58  			},
    59  		},
    60  	}
    61  
    62  	for _, test := range table {
    63  		t.Run(test.name, func(t *testing.T) {
    64  			require.Equal(t, test.res, connsToNodeIDMap(test.source))
    65  		})
    66  	}
    67  }
    68  
    69  type filterFunc func(info balancerConfig.Info, e endpoint.Info) bool
    70  
    71  func (f filterFunc) Allow(info balancerConfig.Info, e endpoint.Info) bool {
    72  	return f(info, e)
    73  }
    74  
    75  func (f filterFunc) String() string {
    76  	return "Custom"
    77  }
    78  
    79  func TestSortPreferConnections(t *testing.T) {
    80  	table := []struct {
    81  		name          string
    82  		source        []conn.Conn
    83  		allowFallback bool
    84  		filter        balancerConfig.Filter
    85  		prefer        []conn.Conn
    86  		fallback      []conn.Conn
    87  	}{
    88  		{
    89  			name:          "Empty",
    90  			source:        nil,
    91  			allowFallback: false,
    92  			filter:        nil,
    93  			prefer:        nil,
    94  			fallback:      nil,
    95  		},
    96  		{
    97  			name: "NilFilter",
    98  			source: []conn.Conn{
    99  				&mock.Conn{AddrField: "1"},
   100  				&mock.Conn{AddrField: "2"},
   101  			},
   102  			allowFallback: false,
   103  			filter:        nil,
   104  			prefer: []conn.Conn{
   105  				&mock.Conn{AddrField: "1"},
   106  				&mock.Conn{AddrField: "2"},
   107  			},
   108  			fallback: nil,
   109  		},
   110  		{
   111  			name: "FilterNoFallback",
   112  			source: []conn.Conn{
   113  				&mock.Conn{AddrField: "t1"},
   114  				&mock.Conn{AddrField: "f1"},
   115  				&mock.Conn{AddrField: "t2"},
   116  				&mock.Conn{AddrField: "f2"},
   117  			},
   118  			allowFallback: false,
   119  			filter: filterFunc(func(_ balancerConfig.Info, e endpoint.Info) bool {
   120  				return strings.HasPrefix(e.Address(), "t")
   121  			}),
   122  			prefer: []conn.Conn{
   123  				&mock.Conn{AddrField: "t1"},
   124  				&mock.Conn{AddrField: "t2"},
   125  			},
   126  			fallback: nil,
   127  		},
   128  		{
   129  			name: "FilterWithFallback",
   130  			source: []conn.Conn{
   131  				&mock.Conn{AddrField: "t1"},
   132  				&mock.Conn{AddrField: "f1"},
   133  				&mock.Conn{AddrField: "t2"},
   134  				&mock.Conn{AddrField: "f2"},
   135  			},
   136  			allowFallback: true,
   137  			filter: filterFunc(func(_ balancerConfig.Info, e endpoint.Info) bool {
   138  				return strings.HasPrefix(e.Address(), "t")
   139  			}),
   140  			prefer: []conn.Conn{
   141  				&mock.Conn{AddrField: "t1"},
   142  				&mock.Conn{AddrField: "t2"},
   143  			},
   144  			fallback: []conn.Conn{
   145  				&mock.Conn{AddrField: "f1"},
   146  				&mock.Conn{AddrField: "f2"},
   147  			},
   148  		},
   149  	}
   150  
   151  	for _, test := range table {
   152  		t.Run(test.name, func(t *testing.T) {
   153  			prefer, fallback := sortPreferConnections(test.source, test.filter, balancerConfig.Info{}, test.allowFallback)
   154  			require.Equal(t, test.prefer, prefer)
   155  			require.Equal(t, test.fallback, fallback)
   156  		})
   157  	}
   158  }
   159  
   160  func TestSelectRandomConnection(t *testing.T) {
   161  	s := newConnectionsState(nil, nil, balancerConfig.Info{}, false)
   162  
   163  	t.Run("Empty", func(t *testing.T) {
   164  		c, failedCount := s.selectRandomConnection(nil, false)
   165  		require.Nil(t, c)
   166  		require.Equal(t, 0, failedCount)
   167  	})
   168  
   169  	t.Run("One", func(t *testing.T) {
   170  		for _, goodState := range []conn.State{conn.Online, conn.Offline, conn.Created} {
   171  			c, failedCount := s.selectRandomConnection([]conn.Conn{&mock.Conn{AddrField: "asd", State: goodState}}, false)
   172  			require.Equal(t, &mock.Conn{AddrField: "asd", State: goodState}, c)
   173  			require.Equal(t, 0, failedCount)
   174  		}
   175  	})
   176  	t.Run("OneBanned", func(t *testing.T) {
   177  		c, failedCount := s.selectRandomConnection([]conn.Conn{&mock.Conn{AddrField: "asd", State: conn.Banned}}, false)
   178  		require.Nil(t, c)
   179  		require.Equal(t, 1, failedCount)
   180  
   181  		c, failedCount = s.selectRandomConnection([]conn.Conn{&mock.Conn{AddrField: "asd", State: conn.Banned}}, true)
   182  		require.Equal(t, &mock.Conn{AddrField: "asd", State: conn.Banned}, c)
   183  		require.Equal(t, 0, failedCount)
   184  	})
   185  	t.Run("Two", func(t *testing.T) {
   186  		conns := []conn.Conn{
   187  			&mock.Conn{AddrField: "1", State: conn.Online},
   188  			&mock.Conn{AddrField: "2", State: conn.Online},
   189  		}
   190  		first := 0
   191  		second := 0
   192  		for i := 0; i < 100; i++ {
   193  			c, _ := s.selectRandomConnection(conns, false)
   194  			if c.Endpoint().Address() == "1" {
   195  				first++
   196  			} else {
   197  				second++
   198  			}
   199  		}
   200  		require.Equal(t, 100, first+second)
   201  		require.InDelta(t, 50, first, 21)
   202  		require.InDelta(t, 50, second, 21)
   203  	})
   204  	t.Run("TwoBanned", func(t *testing.T) {
   205  		conns := []conn.Conn{
   206  			&mock.Conn{AddrField: "1", State: conn.Banned},
   207  			&mock.Conn{AddrField: "2", State: conn.Banned},
   208  		}
   209  		totalFailed := 0
   210  		for i := 0; i < 100; i++ {
   211  			c, failed := s.selectRandomConnection(conns, false)
   212  			require.Nil(t, c)
   213  			totalFailed += failed
   214  		}
   215  		require.Equal(t, 200, totalFailed)
   216  	})
   217  	t.Run("ThreeWithBanned", func(t *testing.T) {
   218  		conns := []conn.Conn{
   219  			&mock.Conn{AddrField: "1", State: conn.Online},
   220  			&mock.Conn{AddrField: "2", State: conn.Online},
   221  			&mock.Conn{AddrField: "3", State: conn.Banned},
   222  		}
   223  		first := 0
   224  		second := 0
   225  		failed := 0
   226  		for i := 0; i < 100; i++ {
   227  			c, checkFailed := s.selectRandomConnection(conns, false)
   228  			failed += checkFailed
   229  			switch c.Endpoint().Address() {
   230  			case "1":
   231  				first++
   232  			case "2":
   233  				second++
   234  			default:
   235  				t.Error(c.Endpoint().Address())
   236  			}
   237  		}
   238  		require.Equal(t, 100, first+second)
   239  		require.InDelta(t, 50, first, 21)
   240  		require.InDelta(t, 50, second, 21)
   241  		require.Greater(t, 10, failed)
   242  	})
   243  }
   244  
   245  func TestNewState(t *testing.T) {
   246  	table := []struct {
   247  		name  string
   248  		state *connectionsState
   249  		res   *connectionsState
   250  	}{
   251  		{
   252  			name:  "Empty",
   253  			state: newConnectionsState(nil, nil, balancerConfig.Info{}, false),
   254  			res: &connectionsState{
   255  				connByNodeID: nil,
   256  				prefer:       nil,
   257  				fallback:     nil,
   258  				all:          nil,
   259  			},
   260  		},
   261  		{
   262  			name: "NoFilter",
   263  			state: newConnectionsState([]conn.Conn{
   264  				&mock.Conn{AddrField: "1", NodeIDField: 1},
   265  				&mock.Conn{AddrField: "2", NodeIDField: 2},
   266  			}, nil, balancerConfig.Info{}, false),
   267  			res: &connectionsState{
   268  				connByNodeID: map[uint32]conn.Conn{
   269  					1: &mock.Conn{AddrField: "1", NodeIDField: 1},
   270  					2: &mock.Conn{AddrField: "2", NodeIDField: 2},
   271  				},
   272  				prefer: []conn.Conn{
   273  					&mock.Conn{AddrField: "1", NodeIDField: 1},
   274  					&mock.Conn{AddrField: "2", NodeIDField: 2},
   275  				},
   276  				fallback: nil,
   277  				all: []conn.Conn{
   278  					&mock.Conn{AddrField: "1", NodeIDField: 1},
   279  					&mock.Conn{AddrField: "2", NodeIDField: 2},
   280  				},
   281  			},
   282  		},
   283  		{
   284  			name: "FilterDenyFallback",
   285  			state: newConnectionsState([]conn.Conn{
   286  				&mock.Conn{AddrField: "t1", NodeIDField: 1, LocationField: "t"},
   287  				&mock.Conn{AddrField: "f1", NodeIDField: 2, LocationField: "f"},
   288  				&mock.Conn{AddrField: "t2", NodeIDField: 3, LocationField: "t"},
   289  				&mock.Conn{AddrField: "f2", NodeIDField: 4, LocationField: "f"},
   290  			}, filterFunc(func(info balancerConfig.Info, e endpoint.Info) bool {
   291  				return info.SelfLocation == e.Location()
   292  			}), balancerConfig.Info{SelfLocation: "t"}, false),
   293  			res: &connectionsState{
   294  				connByNodeID: map[uint32]conn.Conn{
   295  					1: &mock.Conn{AddrField: "t1", NodeIDField: 1, LocationField: "t"},
   296  					2: &mock.Conn{AddrField: "f1", NodeIDField: 2, LocationField: "f"},
   297  					3: &mock.Conn{AddrField: "t2", NodeIDField: 3, LocationField: "t"},
   298  					4: &mock.Conn{AddrField: "f2", NodeIDField: 4, LocationField: "f"},
   299  				},
   300  				prefer: []conn.Conn{
   301  					&mock.Conn{AddrField: "t1", NodeIDField: 1, LocationField: "t"},
   302  					&mock.Conn{AddrField: "t2", NodeIDField: 3, LocationField: "t"},
   303  				},
   304  				fallback: nil,
   305  				all: []conn.Conn{
   306  					&mock.Conn{AddrField: "t1", NodeIDField: 1, LocationField: "t"},
   307  					&mock.Conn{AddrField: "t2", NodeIDField: 3, LocationField: "t"},
   308  				},
   309  			},
   310  		},
   311  		{
   312  			name: "FilterAllowFallback",
   313  			state: newConnectionsState([]conn.Conn{
   314  				&mock.Conn{AddrField: "t1", NodeIDField: 1, LocationField: "t"},
   315  				&mock.Conn{AddrField: "f1", NodeIDField: 2, LocationField: "f"},
   316  				&mock.Conn{AddrField: "t2", NodeIDField: 3, LocationField: "t"},
   317  				&mock.Conn{AddrField: "f2", NodeIDField: 4, LocationField: "f"},
   318  			}, filterFunc(func(info balancerConfig.Info, e endpoint.Info) bool {
   319  				return info.SelfLocation == e.Location()
   320  			}), balancerConfig.Info{SelfLocation: "t"}, true),
   321  			res: &connectionsState{
   322  				connByNodeID: map[uint32]conn.Conn{
   323  					1: &mock.Conn{AddrField: "t1", NodeIDField: 1, LocationField: "t"},
   324  					2: &mock.Conn{AddrField: "f1", NodeIDField: 2, LocationField: "f"},
   325  					3: &mock.Conn{AddrField: "t2", NodeIDField: 3, LocationField: "t"},
   326  					4: &mock.Conn{AddrField: "f2", NodeIDField: 4, LocationField: "f"},
   327  				},
   328  				prefer: []conn.Conn{
   329  					&mock.Conn{AddrField: "t1", NodeIDField: 1, LocationField: "t"},
   330  					&mock.Conn{AddrField: "t2", NodeIDField: 3, LocationField: "t"},
   331  				},
   332  				fallback: []conn.Conn{
   333  					&mock.Conn{AddrField: "f1", NodeIDField: 2, LocationField: "f"},
   334  					&mock.Conn{AddrField: "f2", NodeIDField: 4, LocationField: "f"},
   335  				},
   336  				all: []conn.Conn{
   337  					&mock.Conn{AddrField: "t1", NodeIDField: 1, LocationField: "t"},
   338  					&mock.Conn{AddrField: "f1", NodeIDField: 2, LocationField: "f"},
   339  					&mock.Conn{AddrField: "t2", NodeIDField: 3, LocationField: "t"},
   340  					&mock.Conn{AddrField: "f2", NodeIDField: 4, LocationField: "f"},
   341  				},
   342  			},
   343  		},
   344  		{
   345  			name: "WithNodeID",
   346  			state: newConnectionsState([]conn.Conn{
   347  				&mock.Conn{AddrField: "t1", NodeIDField: 1, LocationField: "t"},
   348  				&mock.Conn{AddrField: "f1", NodeIDField: 2, LocationField: "f"},
   349  				&mock.Conn{AddrField: "t2", NodeIDField: 3, LocationField: "t"},
   350  				&mock.Conn{AddrField: "f2", NodeIDField: 4, LocationField: "f"},
   351  			}, filterFunc(func(info balancerConfig.Info, e endpoint.Info) bool {
   352  				return info.SelfLocation == e.Location()
   353  			}), balancerConfig.Info{SelfLocation: "t"}, true),
   354  			res: &connectionsState{
   355  				connByNodeID: map[uint32]conn.Conn{
   356  					1: &mock.Conn{AddrField: "t1", NodeIDField: 1, LocationField: "t"},
   357  					2: &mock.Conn{AddrField: "f1", NodeIDField: 2, LocationField: "f"},
   358  					3: &mock.Conn{AddrField: "t2", NodeIDField: 3, LocationField: "t"},
   359  					4: &mock.Conn{AddrField: "f2", NodeIDField: 4, LocationField: "f"},
   360  				},
   361  				prefer: []conn.Conn{
   362  					&mock.Conn{AddrField: "t1", NodeIDField: 1, LocationField: "t"},
   363  					&mock.Conn{AddrField: "t2", NodeIDField: 3, LocationField: "t"},
   364  				},
   365  				fallback: []conn.Conn{
   366  					&mock.Conn{AddrField: "f1", NodeIDField: 2, LocationField: "f"},
   367  					&mock.Conn{AddrField: "f2", NodeIDField: 4, LocationField: "f"},
   368  				},
   369  				all: []conn.Conn{
   370  					&mock.Conn{AddrField: "t1", NodeIDField: 1, LocationField: "t"},
   371  					&mock.Conn{AddrField: "f1", NodeIDField: 2, LocationField: "f"},
   372  					&mock.Conn{AddrField: "t2", NodeIDField: 3, LocationField: "t"},
   373  					&mock.Conn{AddrField: "f2", NodeIDField: 4, LocationField: "f"},
   374  				},
   375  			},
   376  		},
   377  	}
   378  
   379  	for _, test := range table {
   380  		t.Run(test.name, func(t *testing.T) {
   381  			require.NotNil(t, test.state.rand)
   382  			test.state.rand = nil
   383  			require.Equal(t, test.res, test.state)
   384  		})
   385  	}
   386  }
   387  
   388  func TestConnection(t *testing.T) {
   389  	t.Run("Empty", func(t *testing.T) {
   390  		s := newConnectionsState(nil, nil, balancerConfig.Info{}, false)
   391  		c, failed := s.GetConnection(context.Background())
   392  		require.Nil(t, c)
   393  		require.Equal(t, 0, failed)
   394  	})
   395  	t.Run("AllGood", func(t *testing.T) {
   396  		s := newConnectionsState([]conn.Conn{
   397  			&mock.Conn{AddrField: "1", State: conn.Online},
   398  			&mock.Conn{AddrField: "2", State: conn.Online},
   399  		}, nil, balancerConfig.Info{}, false)
   400  		c, failed := s.GetConnection(context.Background())
   401  		require.NotNil(t, c)
   402  		require.Equal(t, 0, failed)
   403  	})
   404  	t.Run("WithBanned", func(t *testing.T) {
   405  		s := newConnectionsState([]conn.Conn{
   406  			&mock.Conn{AddrField: "1", State: conn.Online},
   407  			&mock.Conn{AddrField: "2", State: conn.Banned},
   408  		}, nil, balancerConfig.Info{}, false)
   409  		c, _ := s.GetConnection(context.Background())
   410  		require.Equal(t, &mock.Conn{AddrField: "1", State: conn.Online}, c)
   411  	})
   412  	t.Run("AllBanned", func(t *testing.T) {
   413  		s := newConnectionsState([]conn.Conn{
   414  			&mock.Conn{AddrField: "t1", State: conn.Banned, LocationField: "t"},
   415  			&mock.Conn{AddrField: "f2", State: conn.Banned, LocationField: "f"},
   416  		}, filterFunc(func(info balancerConfig.Info, e endpoint.Info) bool {
   417  			return e.Location() == info.SelfLocation
   418  		}), balancerConfig.Info{}, true)
   419  		preferred := 0
   420  		fallback := 0
   421  		for i := 0; i < 100; i++ {
   422  			c, failed := s.GetConnection(context.Background())
   423  			require.NotNil(t, c)
   424  			require.Equal(t, 2, failed)
   425  			if c.Endpoint().Address() == "t1" {
   426  				preferred++
   427  			} else {
   428  				fallback++
   429  			}
   430  		}
   431  		require.Equal(t, 100, preferred+fallback)
   432  		require.InDelta(t, 50, preferred, 21)
   433  		require.InDelta(t, 50, fallback, 21)
   434  	})
   435  	t.Run("PreferBannedWithFallback", func(t *testing.T) {
   436  		s := newConnectionsState([]conn.Conn{
   437  			&mock.Conn{AddrField: "t1", State: conn.Banned, LocationField: "t"},
   438  			&mock.Conn{AddrField: "f2", State: conn.Online, LocationField: "f"},
   439  		}, filterFunc(func(info balancerConfig.Info, e endpoint.Info) bool {
   440  			return e.Location() == info.SelfLocation
   441  		}), balancerConfig.Info{SelfLocation: "t"}, true)
   442  		c, failed := s.GetConnection(context.Background())
   443  		require.Equal(t, &mock.Conn{AddrField: "f2", State: conn.Online, LocationField: "f"}, c)
   444  		require.Equal(t, 1, failed)
   445  	})
   446  	t.Run("PreferNodeID", func(t *testing.T) {
   447  		s := newConnectionsState([]conn.Conn{
   448  			&mock.Conn{AddrField: "1", State: conn.Online, NodeIDField: 1},
   449  			&mock.Conn{AddrField: "2", State: conn.Online, NodeIDField: 2},
   450  		}, nil, balancerConfig.Info{}, false)
   451  		c, failed := s.GetConnection(endpoint.WithNodeID(context.Background(), 2))
   452  		require.Equal(t, &mock.Conn{AddrField: "2", State: conn.Online, NodeIDField: 2}, c)
   453  		require.Equal(t, 0, failed)
   454  	})
   455  	t.Run("PreferNodeIDWithBadState", func(t *testing.T) {
   456  		s := newConnectionsState([]conn.Conn{
   457  			&mock.Conn{AddrField: "1", State: conn.Online, NodeIDField: 1},
   458  			&mock.Conn{AddrField: "2", State: conn.Unknown, NodeIDField: 2},
   459  		}, nil, balancerConfig.Info{}, false)
   460  		c, failed := s.GetConnection(endpoint.WithNodeID(context.Background(), 2))
   461  		require.Equal(t, &mock.Conn{AddrField: "1", State: conn.Online, NodeIDField: 1}, c)
   462  		require.Equal(t, 0, failed)
   463  	})
   464  }